Merge "backuptool: Unmount v3 partitions during backup" into typhoon
diff --git a/build/tools/roomservice.py b/build/tools/roomservice.py
index 25389b7..7a361fd 100755
--- a/build/tools/roomservice.py
+++ b/build/tools/roomservice.py
@@ -1,144 +1,89 @@
-#!/usr/bin/env python2
-
-# Copyright (C) 2013 Cybojenix <anthonydking@gmail.com>
-# Copyright (C) 2013 The OmniROM Project
-# Copyright (C) 2015 BlissRoms Project
+#!/usr/bin/env python
+# Copyright (C) 2012-2013, The CyanogenMod Project
+# Copyright (C) 2012-2015, SlimRoms Project
+# Copyright (C) 2016-2017, AOSiP
 #
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+#      http://www.apache.org/licenses/LICENSE-2.0
 #
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
 
 from __future__ import print_function
+
+import base64
 import json
-import sys
+import netrc
 import os
-import re
-from xml.etree import ElementTree as ES
-# Use the urllib importer from the Cyanogenmod roomservice
+import sys
+
+from xml.etree import ElementTree
+
 try:
     # For python3
+    import urllib.error
+    import urllib.parse
     import urllib.request
 except ImportError:
     # For python2
     import imp
     import urllib2
+    import urlparse
     urllib = imp.new_module('urllib')
+    urllib.error = urllib2
+    urllib.parse = urlparse
     urllib.request = urllib2
 
-# Config
-# set this to the default remote to use in repo
-default_rem = "github"
-# set this to the default revision to use (branch/tag name)
-default_rev = "typhoon"
-# set this to the remote that you use for projects from your team repos
-# example fetch="https://github.com/BlissRoms-Devices"
-default_team_rem = "github"
-# this shouldn't change unless google makes changes
-local_manifest_dir = ".repo/local_manifests"
-# change this to your name on github (or equivalent hosting)
-android_team = "BlissRoms-Devices"
+DEBUG = False
+
+custom_local_manifest = ".repo/local_manifests/roomservice.xml"
+custom_default_revision =  os.getenv('ROOMSERVICE_DEFAULT_BRANCH', 'typhoon')
+custom_dependencies = "bliss.dependencies"
+org_manifest = "BlissRoms-Devices"  # leave empty if org is provided in manifest
+org_display = "BlissRoms-Devices"  # needed for displaying
+
+github_auth = None
 
 
-def check_repo_exists(git_data):
-    if not int(git_data.get('total_count', 0)):
-        raise Exception("{} not found in {} Github, exiting "
-                        "roomservice".format(device, android_team))
+local_manifests = '.repo/local_manifests'
+if not os.path.exists(local_manifests):
+    os.makedirs(local_manifests)
 
 
-# Note that this can only be done 5 times per minute
-def search_github_for_device(device):
-    git_device = '+'.join(re.findall('[a-z]+|[\d]+',  device))
-    git_search_url = "https://api.github.com/search/repositories" \
-                     "?q=%40{}+android_device+{}+fork:true".format(android_team, git_device)
-    git_req = urllib.request.Request(git_search_url)
-    try:
-        response = urllib.request.urlopen(git_req)
-    except urllib.request.HTTPError:
-        raise Exception("There was an issue connecting to github."
-                        " Please try again in a minute")
-    git_data = json.load(response)
-    check_repo_exists(git_data)
-    print("found the {} device repo".format(device))
-    return git_data
+def debug(*args, **kwargs):
+    if DEBUG:
+        print(*args, **kwargs)
 
 
-def get_device_url(git_data):
-    device_url = ""
-    for item in git_data['items']:
-        temp_url = item.get('html_url')
-        if "{}/android_device".format(android_team) in temp_url:
-            try:
-                temp_url = temp_url[temp_url.index("android_device"):]
-            except ValueError:
-                pass
-            else:
-                if temp_url.endswith(device):
-                    device_url = temp_url
-                    break
-
-    if device_url:
-        return device_url
-    raise Exception("{} not found in {} Github, exiting "
-                    "roomservice".format(device, android_team))
-
-
-def parse_device_directory(device_url,device):
-    to_strip = "android_device"
-    repo_name = device_url[device_url.index(to_strip) + len(to_strip):]
-    repo_name = repo_name[:repo_name.index(device)]
-    repo_dir = repo_name.replace("_", "/")
-    repo_dir = repo_dir + device
-    return "device{}".format(repo_dir)
-
-
-# Thank you RaYmAn
-def iterate_manifests(check_all):
-    files = []
-    if check_all:
-        for file in os.listdir(local_manifest_dir):
-            if file.endswith('.xml'):
-                files.append(os.path.join(local_manifest_dir, file))
-    files.append('.repo/manifest.xml')
-    for file in files:
+def add_auth(g_req):
+    global github_auth
+    if github_auth is None:
         try:
-            man = ES.parse(file)
-            man = man.getroot()
-        except IOError, ES.ParseError:
-            print("WARNING: error while parsing %s" % file)
+            auth = netrc.netrc().authenticators("api.github.com")
+        except (netrc.NetrcParseError, IOError):
+            auth = None
+        if auth:
+            github_auth = base64.b64encode(
+                ('%s:%s' % (auth[0], auth[2])).encode()
+            )
         else:
-            for project in man.findall("project"):
-                yield project
+            github_auth = ""
+    if github_auth:
+        g_req.add_header("Authorization", "Basic %s" % github_auth)
 
 
-def check_project_exists(url):
-    for project in iterate_manifests(True):
-        if project.get("name") == url:
-            return True
-    return False
-
-
-def check_dup_path(directory):
-    for project in iterate_manifests(False):
-        if project.get("path") == directory:
-            print ("Duplicate path %s found! Removing" % directory)
-            return project.get("name")
-    return None
-
-# Use the indent function from http://stackoverflow.com/a/4590052
 def indent(elem, level=0):
-    i = ''.join(["\n", level*"  "])
+    # in-place prettyprint formatter
+    i = "\n" + "  " * level
     if len(elem):
         if not elem.text or not elem.text.strip():
-            elem.text = ''.join([i, "  "])
+            elem.text = i + "  "
         if not elem.tail or not elem.tail.strip():
             elem.tail = i
         for elem in elem:
@@ -149,169 +94,224 @@
         if level and (not elem.tail or not elem.tail.strip()):
             elem.tail = i
 
-
-def create_manifest_project(url, directory,
-                            remote=default_rem,
-                            revision=default_rev):
-    project_exists = check_project_exists(url)
-
-    if project_exists:
-        return None
-
-    dup_path = check_dup_path(directory)
-    if not dup_path is None:
-            write_to_manifest(
-                append_to_manifest(
-                    create_manifest_remove(dup_path)))
-
-    project = ES.Element("project",
-                         attrib={
-                             "path": directory,
-                             "name": url,
-                             "remote": remote,
-                             "revision": revision
-                         })
-    return project
-
-
-def create_manifest_remove(url):
-    remove = ES.Element("remove-project", attrib={"name": url})
-    return remove
-
-
-def append_to_manifest(project):
+def load_manifest(manifest):
     try:
-        lm = ES.parse('/'.join([local_manifest_dir, "roomservice.xml"]))
-        lm = lm.getroot()
-    except IOError, ES.ParseError:
-        lm = ES.Element("manifest")
-    lm.append(project)
-    return lm
+        man = ElementTree.parse(manifest).getroot()
+    except (IOError, ElementTree.ParseError):
+        man = ElementTree.Element("manifest")
+    return man
 
-
-def write_to_manifest(manifest):
-    indent(manifest)
-    raw_xml = ES.tostring(manifest).decode()
-    raw_xml = ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n'
-                       '<!--Please do not manually edit this file-->\n',
-                       raw_xml])
-
-    with open('/'.join([local_manifest_dir, "roomservice.xml"]), 'w') as f:
-        f.write(raw_xml)
-    print("wrote the new roomservice manifest")
-
-def parse_device_from_manifest(device):
-    for project in iterate_manifests(True):
-        name = project.get('name')
-        if name.startswith("android_device_") and name.endswith(device):
-            return project.get('path')
+def get_from_manifest(device_name):
+    if os.path.exists(custom_local_manifest):
+        man = load_manifest(custom_local_manifest)
+        for local_path in man.findall("project"):
+            lp = local_path.get("path").strip('/')
+            if lp.startswith("device/") and lp.endswith("/" + device_name):
+                return lp
     return None
 
 
-def parse_device_from_folder(device):
-    search = []
-    for sub_folder in os.listdir("device"):
-        if os.path.isdir("device/%s/%s" % (sub_folder, device)):
-            search.append("device/%s/%s" % (sub_folder, device))
-    if len(search) > 1:
-        print("multiple devices under the name %s. "
-              "defaulting to checking the manifest" % device)
-        location = parse_device_from_manifest(device)
-    elif len(search) == 1:
-        location = search[0]
-    else:
-        print("your device can't be found in device sources..")
-        location = parse_device_from_manifest(device)
-    return location
+def is_in_manifest(project_path):
+    man = load_manifest(custom_local_manifest)
+    for local_path in man.findall("project"):
+        if local_path.get("path") == project_path:
+            return True
+    return False
 
 
-def parse_dependency_file(location):
-    dep_file = "bliss.dependencies"
-    dep_location = '/'.join([location, dep_file])
-    if not os.path.isfile(dep_location):
-        print("WARNING: %s file not found" % dep_location)
-        sys.exit()
-    try:
-        with open(dep_location, 'r') as f:
-            dependencies = json.loads(f.read())
-    except ValueError:
-        raise Exception("ERROR: malformed dependency file")
-    return dependencies
+def add_to_manifest(repos, fallback_branch=None):
+    lm = load_manifest(custom_local_manifest)
+
+    for repo in repos:
+        repo_name = repo['repository']
+        repo_path = repo['target_path']
+        if 'branch' in repo:
+            repo_branch=repo['branch']
+        else:
+            repo_branch=custom_default_revision
+        if 'remote' in repo:
+            repo_remote=repo['remote']
+        elif "/" not in repo_name:
+            repo_remote=org_manifest
+        elif "/" in repo_name:
+            repo_remote="github"
+
+        if is_in_manifest(repo_path):
+            print('already exists: %s' % repo_path)
+            continue
+
+        print('Adding dependency:\nRepository: %s\nBranch: %s\nRemote: %s\nPath: %s\n' % (repo_name, repo_branch,repo_remote, repo_path))
+
+        project = ElementTree.Element(
+            "project",
+            attrib={"path": repo_path,
+                    "remote": repo_remote,
+                    "name": "%s" % repo_name}
+        )
+
+        clone_depth = os.getenv('ROOMSERVICE_CLONE_DEPTH')
+        if clone_depth:
+            project.set('clone-depth', clone_depth)
+
+        if repo_branch is not None:
+            project.set('revision', repo_branch)
+        elif fallback_branch:
+            print("Using branch %s for %s" %
+                  (fallback_branch, repo_name))
+            project.set('revision', fallback_branch)
+        else:
+            print("Using default branch for %s" % repo_name)
+        if 'clone-depth' in repo:
+            print("Setting clone-depth to %s for %s" % (repo['clone-depth'], repo_name))
+            project.set('clone-depth', repo['clone-depth'])
+        lm.append(project)
+
+    indent(lm)
+    raw_xml = "\n".join(('<?xml version="1.0" encoding="UTF-8"?>',
+                         ElementTree.tostring(lm).decode()))
+
+    f = open(custom_local_manifest, 'w')
+    f.write(raw_xml)
+    f.close()
+
+_fetch_dep_cache = []
 
 
-def create_dependency_manifest(dependencies):
-    projects = []
-    for dependency in dependencies:
-        repository = dependency.get("repository")
-        target_path = dependency.get("target_path")
-        revision = dependency.get("revision", default_rev)
-        remote = dependency.get("remote", default_rem)
-
-        # not adding an organization should default to android_team
-        # only apply this to github
-        if remote == "github":
-            if not "/" in repository:
-                repository = '/'.join([android_team, repository])
-        project = create_manifest_project(repository,
-                                          target_path,
-                                          remote=remote,
-                                          revision=revision)
-        if not project is None:
-            manifest = append_to_manifest(project)
-            write_to_manifest(manifest)
-            projects.append(target_path)
-    if len(projects) > 0:
-        os.system("repo sync --force-sync %s" % " ".join(projects))
-
-
-def fetch_dependencies(device):
-    location = parse_device_from_folder(device)
-    if location is None or not os.path.isdir(location):
-        raise Exception("ERROR: could not find your device "
-                        "folder location, bailing out")
-    dependencies = parse_dependency_file(location)
-    create_dependency_manifest(dependencies)
-
-
-def check_device_exists(device):
-    location = parse_device_from_folder(device)
-    if location is None:
-        return False
-    return os.path.isdir(location)
-
-
-def fetch_device(device):
-    if check_device_exists(device):
-        print("WARNING: Trying to fetch a device that's already there")
+def fetch_dependencies(repo_path, fallback_branch=None):
+    global _fetch_dep_cache
+    if repo_path in _fetch_dep_cache:
         return
-    git_data = search_github_for_device(device)
-    device_url = android_team+"/"+get_device_url(git_data)
-    device_dir = parse_device_directory(device_url,device)
-    project = create_manifest_project(device_url,
-                                      device_dir,
-                                      remote=default_team_rem)
-    if not project is None:
-        manifest = append_to_manifest(project)
-        write_to_manifest(manifest)
-        print("syncing the device config")
-        os.system('repo sync --force-sync %s' % device_dir)
+    _fetch_dep_cache.append(repo_path)
+
+    print('Looking for dependencies')
+
+    dep_p = '/'.join((repo_path, custom_dependencies))
+    if os.path.exists(dep_p):
+        with open(dep_p) as dep_f:
+            dependencies = json.load(dep_f)
+    else:
+        dependencies = {}
+        print('%s has no additional dependencies.' % repo_path)
+
+    fetch_list = []
+    syncable_repos = []
+
+    for dependency in dependencies:
+        if not is_in_manifest(dependency['target_path']):
+            if not dependency.get('branch'):
+                dependency['branch'] = custom_default_revision
+
+            fetch_list.append(dependency)
+            syncable_repos.append(dependency['target_path'])
+        else:
+            print("Dependency already present in manifest: %s => %s" % (dependency['repository'], dependency['target_path']))
+
+    if fetch_list:
+        print('Adding dependencies to manifest\n')
+        add_to_manifest(fetch_list, fallback_branch)
+
+    if syncable_repos:
+        print('Syncing dependencies')
+        os.system('repo sync --force-sync --no-tags --current-branch --no-clone-bundle %s' % ' '.join(syncable_repos))
+
+    for deprepo in syncable_repos:
+        fetch_dependencies(deprepo)
 
 
-if __name__ == '__main__':
-    if not os.path.isdir(local_manifest_dir):
-        os.mkdir(local_manifest_dir)
+def has_branch(branches, revision):
+    return revision in (branch['name'] for branch in branches)
+
+
+def detect_revision(repo):
+    """
+    returns None if using the default revision, else return
+    the branch name if using a different revision
+    """
+    print("Checking branch info")
+    githubreq = urllib.request.Request(
+        repo['branches_url'].replace('{/branch}', ''))
+    add_auth(githubreq)
+    result = json.loads(urllib.request.urlopen(githubreq).read().decode())
+
+    print("Calculated revision: %s" % custom_default_revision)
+
+    if has_branch(result, custom_default_revision):
+        return custom_default_revision
+
+    print("Branch %s not found" % custom_default_revision)
+    sys.exit()
+
+
+def main():
+    global DEBUG
+    try:
+        depsonly = bool(sys.argv[2] in ['true', 1])
+    except IndexError:
+        depsonly = False
+
+    if os.getenv('ROOMSERVICE_DEBUG'):
+        DEBUG = True
 
     product = sys.argv[1]
+    device = product[product.find("_") + 1:] or product
+
+    if depsonly:
+        repo_path = get_from_manifest(device)
+        if repo_path:
+            fetch_dependencies(repo_path)
+        else:
+            print("Trying dependencies-only mode on a "
+                  "non-existing device tree?")
+        sys.exit()
+
+    print("Device {0} not found. Attempting to retrieve device repository from "
+          "{1} Github (http://github.com/{1}).".format(device, org_display))
+
+    githubreq = urllib.request.Request(
+        "https://api.github.com/search/repositories?"
+        "q={0}+user:{1}+in:name+fork:true".format(device, org_display))
+    add_auth(githubreq)
+
+    repositories = []
+
     try:
-        device = product[product.index("_") + 1:]
+        result = json.loads(urllib.request.urlopen(githubreq).read().decode())
+    except urllib.error.URLError:
+        print("Failed to search GitHub")
+        sys.exit(1)
     except ValueError:
-        device = product
+        print("Failed to parse return data from GitHub")
+        sys.exit(1)
+    for res in result.get('items', []):
+        repositories.append(res)
 
-    if len(sys.argv) > 2:
-        deps_only = sys.argv[2]
-    else:
-        deps_only = False
+    for repository in repositories:
+        repo_name = repository['name']
 
-    if not deps_only:
-        fetch_device(device)
-    fetch_dependencies(device)
+        if not (repo_name.startswith("android_device") and
+                repo_name.endswith("_" + device)):
+            continue
+        print("Found repository: %s" % repository['name'])
+
+        fallback_branch = detect_revision(repository)
+        manufacturer = repo_name[15:-(len(device)+1)]
+        repo_path = "device/%s/%s" % (manufacturer, device)
+        adding = [{'repository': repo_name, 'target_path': repo_path}]
+
+        add_to_manifest(adding, fallback_branch)
+
+        print("Syncing repository to retrieve project.")
+        os.system('repo sync --force-sync --no-tags --current-branch --no-clone-bundle %s' % repo_path)
+        print("Repository synced!")
+
+        fetch_dependencies(repo_path, fallback_branch)
+        print("Done")
+        sys.exit()
+
+    print("Repository for %s not found in the %s Github repository list."
+          % (device, org_display))
+    print("If this is in error, you may need to manually add it to your "
+          "%s" % custom_local_manifest)
+
+if __name__ == "__main__":
+    main()
diff --git a/config/bliss_packages.mk b/config/bliss_packages.mk
index d55f271..3717e3c 100644
--- a/config/bliss_packages.mk
+++ b/config/bliss_packages.mk
@@ -11,10 +11,7 @@
     Etar \
     ExactCalculator \
     Exchange2 \
-    messaging \
-    SimpleGalleryPro \
-    SimpleCalculator \
-    SimpleCalendar
+    messaging
 endif
 
 # Bliss Packages
diff --git a/config/common.mk b/config/common.mk
index 523ac17..5583fb4 100644
--- a/config/common.mk
+++ b/config/common.mk
@@ -176,6 +176,9 @@
 # Bliss Packages
 -include vendor/bliss/config/bliss_packages.mk
 
+# Bliss Prebuilts
+-include vendor/prebuilts/bliss_prebuilts.mk
+
 # Bliss Overlays
 -include vendor/overlays/bliss_overlays.mk
 
diff --git a/config/versions.mk b/config/versions.mk
index 15b2b72..0495ca3 100644
--- a/config/versions.mk
+++ b/config/versions.mk
@@ -1,7 +1,7 @@
 # Versioning System For Bliss
 # Bliss RELEASE VERSION
 BLISS_VERSION_MAJOR = 16
-BLISS_VERSION_MINOR = 3
+BLISS_VERSION_MINOR = 4
 BLISS_CODENAME = Typhoon
 #BLISS_VERSION_MAINTENANCE = Beta
 
@@ -51,3 +51,4 @@
 BLISS_DISPLAY_BUILDTYPE := $(BLISS_BUILDTYPE)
 BLISS_FINGERPRINT := Bliss/$(VERSION)/$(TARGET_PRODUCT_SHORT)/$(shell date +%Y%m%d)
 BLISS_BUILD_TIMESTAMP := $(shell date +%Y%m%d)
+BLISS_BUILD_VERSION := $(BLISS_BUILD_ZIP)
diff --git a/prebuilt/common/Android.mk b/prebuilt/common/Android.mk
index c85a140..d985869 100644
--- a/prebuilt/common/Android.mk
+++ b/prebuilt/common/Android.mk
@@ -44,17 +44,6 @@
 include $(BUILD_PREBUILT)
 
 include $(CLEAR_VARS)
-LOCAL_MODULE := SimpleGalleryPro
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := app/$(LOCAL_MODULE).apk
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_MODULE_CLASS := APPS
-LOCAL_OVERRIDES_PACKAGES := Gallery2
-LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
-LOCAL_DEX_PREOPT := false
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := AboutBliss
 LOCAL_MODULE_TAGS := optional
 LOCAL_SRC_FILES := app/$(LOCAL_MODULE).apk
@@ -63,24 +52,3 @@
 LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
 LOCAL_DEX_PREOPT := false
 include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := SimpleCalculator
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := app/$(LOCAL_MODULE).apk
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_MODULE_CLASS := APPS
-LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
-LOCAL_DEX_PREOPT := false
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := SimpleCalendar
-LOCAL_MODULE_TAGS := optional
-LOCAL_SRC_FILES := app/$(LOCAL_MODULE).apk
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_MODULE_CLASS := APPS
-LOCAL_OVERRIDES_PACKAGES := Etar Calendar
-LOCAL_MODULE_SUFFIX := $(COMMON_ANDROID_PACKAGE_SUFFIX)
-LOCAL_DEX_PREOPT := false
-include $(BUILD_PREBUILT)
diff --git a/prebuilt/common/app/SimpleCalculator.apk b/prebuilt/common/app/SimpleCalculator.apk
deleted file mode 100644
index e43fbd5..0000000
--- a/prebuilt/common/app/SimpleCalculator.apk
+++ /dev/null
Binary files differ
diff --git a/prebuilt/common/app/SimpleCalendar.apk b/prebuilt/common/app/SimpleCalendar.apk
deleted file mode 100644
index 5c719bf..0000000
--- a/prebuilt/common/app/SimpleCalendar.apk
+++ /dev/null
Binary files differ
diff --git a/prebuilt/common/app/SimpleGalleryPro.apk b/prebuilt/common/app/SimpleGalleryPro.apk
deleted file mode 100644
index 47d077f..0000000
--- a/prebuilt/common/app/SimpleGalleryPro.apk
+++ /dev/null
Binary files differ