Updater: Implement leaf updates

Change-Id: I02a74807604851a977f5e16c692c21361c64f03a
diff --git a/README.md b/README.md
index ecb06f4..fd6eb6e 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 Server requirements
 -------------------
 The app sends `GET` requests to the URL defined by the `updater_server_url`
-resource (or the `lineage.updater.uri` system property) and expects as response
+resource (or the `leaf.updater.uri` system property) and expects as response
 a JSON with the following structure:
 ```json
 {
@@ -18,7 +18,8 @@
       "romtype": "nightly",
       "size": 314572800,
       "url": "https://example.com/ota-package.zip",
-      "version": "15.1"
+      "version": "3.0",
+      "upgrade": "2.0"
     }
   ]
 }
@@ -31,6 +32,7 @@
 The `size` attribute is the size of the update expressed in bytes.  
 The `url` attribute is the URL of the file to be downloaded.  
 The `version` attribute is the string to be compared with the `ro.lineage.build.version` property.  
+The `upgrade` attribute is the minimum version supported for automatic upgrades to the current build.  
 
 Additional attributes are ignored.
 
diff --git a/app/src/main/java/org/lineageos/updater/misc/Constants.java b/app/src/main/java/org/lineageos/updater/misc/Constants.java
index 0aee507..49bad25 100644
--- a/app/src/main/java/org/lineageos/updater/misc/Constants.java
+++ b/app/src/main/java/org/lineageos/updater/misc/Constants.java
@@ -39,13 +39,14 @@
 
     public static final String PROP_AB_DEVICE = "ro.build.ab_update";
     public static final String PROP_BUILD_DATE = "ro.build.date.utc";
-    public static final String PROP_BUILD_VERSION = "ro.lineage.build.version";
+    public static final String PROP_BUILD_VERSION = "ro.leaf.build.version";
     public static final String PROP_BUILD_VERSION_INCREMENTAL = "ro.build.version.incremental";
-    public static final String PROP_DEVICE = "ro.lineage.device";
+    public static final String PROP_DEVICE = "ro.product.device";
     public static final String PROP_NEXT_DEVICE = "ro.updater.next_device";
-    public static final String PROP_RELEASE_TYPE = "ro.lineage.releasetype";
-    public static final String PROP_UPDATER_ALLOW_DOWNGRADING = "lineage.updater.allow_downgrading";
-    public static final String PROP_UPDATER_URI = "lineage.updater.uri";
+    public static final String PROP_RELEASE_TYPE = "ro.leaf.releasetype";
+    public static final String PROP_FLAVOR = "ro.leaf.flavor";
+    public static final String PROP_UPDATER_ALLOW_DOWNGRADING = "leaf.updater.allow_downgrading";
+    public static final String PROP_UPDATER_URI = "leaf.updater.uri";
 
     public static final String PREF_INSTALL_OLD_TIMESTAMP = "install_old_timestamp";
     public static final String PREF_INSTALL_NEW_TIMESTAMP = "install_new_timestamp";
diff --git a/app/src/main/java/org/lineageos/updater/misc/Utils.java b/app/src/main/java/org/lineageos/updater/misc/Utils.java
index 3bdd509..b1e90b5 100644
--- a/app/src/main/java/org/lineageos/updater/misc/Utils.java
+++ b/app/src/main/java/org/lineageos/updater/misc/Utils.java
@@ -92,6 +92,8 @@
         update.setFileSize(object.getLong("size"));
         update.setDownloadUrl(object.getString("url"));
         update.setVersion(object.getString("version"));
+        if (object.has("upgrade"))
+            update.setUpgradeMinVersion(object.getString("upgrade"));
         return update;
     }
 
@@ -114,7 +116,10 @@
 
     public static boolean canInstall(UpdateBaseInfo update) {
         return (SystemProperties.getBoolean(Constants.PROP_UPDATER_ALLOW_DOWNGRADING, false) ||
-                update.getTimestamp() > SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0));
+                update.getTimestamp() > SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0)) &&
+                (update.getUpgradeMinVersion() == null || update.getUpgradeMinVersion().length() == 0 ||
+                        Version.parse(SystemProperties.get(Constants.PROP_BUILD_VERSION))
+                        .compareTo(Version.parse(update.getUpgradeMinVersion())) >= 0);
     }
 
     public static List<UpdateInfo> parseJson(File file, boolean compatibleOnly)
@@ -154,6 +159,7 @@
         String device = SystemProperties.get(Constants.PROP_NEXT_DEVICE,
                 SystemProperties.get(Constants.PROP_DEVICE));
         String type = SystemProperties.get(Constants.PROP_RELEASE_TYPE).toLowerCase(Locale.ROOT);
+        String flavor = SystemProperties.get(Constants.PROP_FLAVOR);
 
         String serverUrl = SystemProperties.get(Constants.PROP_UPDATER_URI);
         if (serverUrl.trim().isEmpty()) {
@@ -162,7 +168,8 @@
 
         return serverUrl.replace("{device}", device)
                 .replace("{type}", type)
-                .replace("{incr}", incrementalVersion);
+                .replace("{incr}", incrementalVersion)
+                .replace("{flavor}", flavor);
     }
 
     public static String getUpgradeBlockedURL(Context context) {
diff --git a/app/src/main/java/org/lineageos/updater/misc/Version.java b/app/src/main/java/org/lineageos/updater/misc/Version.java
new file mode 100644
index 0000000..2ce1b15
--- /dev/null
+++ b/app/src/main/java/org/lineageos/updater/misc/Version.java
@@ -0,0 +1,303 @@
+package org.lineageos.updater.misc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A module's version string.
+ *
+ * <p> A version string has three components: The version number itself, an
+ * optional pre-release version, and an optional build version.  Each
+ * component is a sequence of tokens; each token is either a non-negative
+ * integer or a string.  Tokens are separated by the punctuation characters
+ * {@code '.'}, {@code '-'}, or {@code '+'}, or by transitions from a
+ * sequence of digits to a sequence of characters that are neither digits
+ * nor punctuation characters, or vice versa.  Consecutive repeated
+ * punctuation characters are treated as a single punctuation character.
+ *
+ * <ul>
+ *
+ *   <li> The <i>version number</i> is a sequence of tokens separated by
+ *   {@code '.'} characters, terminated by the first {@code '-'} or {@code
+ *   '+'} character. </li>
+ *
+ *   <li> The <i>pre-release version</i> is a sequence of tokens separated
+ *   by {@code '.'} or {@code '-'} characters, terminated by the first
+ *   {@code '+'} character. </li>
+ *
+ *   <li> The <i>build version</i> is a sequence of tokens separated by
+ *   {@code '.'}, {@code '-'}, or {@code '+'} characters.
+ *
+ * </ul>
+ *
+ * <p> When comparing two version strings, the elements of their
+ * corresponding components are compared in pointwise fashion.  If one
+ * component is longer than the other, but otherwise equal to it, then the
+ * first component is considered the greater of the two; otherwise, if two
+ * corresponding elements are integers then they are compared as such;
+ * otherwise, at least one of the elements is a string, so the other is
+ * converted into a string if it is an integer and the two are compared
+ * lexicographically.  Trailing integer elements with the value zero are
+ * ignored.
+ *
+ * <p> Given two version strings, if their version numbers differ then the
+ * result of comparing them is the result of comparing their version
+ * numbers; otherwise, if one of them has a pre-release version but the
+ * other does not then the first is considered to precede the second,
+ * otherwise the result of comparing them is the result of comparing their
+ * pre-release versions; otherwise, the result of comparing them is the
+ * result of comparing their build versions.
+ *
+ * @see ModuleDescriptor#version()
+ * @since 9
+ */
+
+public final class Version implements Comparable<Version> {
+
+    private final String version;
+
+    // If Java had disjunctive types then we'd write List<Integer|String> here
+    //
+    private final List<Object> sequence;
+    private final List<Object> pre;
+    private final List<Object> build;
+
+    // Take a numeric token starting at position i
+    // Append it to the given list
+    // Return the index of the first character not taken
+    // Requires: s.charAt(i) is (decimal) numeric
+    //
+    private static int takeNumber(String s, int i, List<Object> acc) {
+        char c = s.charAt(i);
+        int d = (c - '0');
+        int n = s.length();
+        while (++i < n) {
+            c = s.charAt(i);
+            if (c >= '0' && c <= '9') {
+                d = d * 10 + (c - '0');
+                continue;
+            }
+            break;
+        }
+        acc.add(d);
+        return i;
+    }
+
+    // Take a string token starting at position i
+    // Append it to the given list
+    // Return the index of the first character not taken
+    // Requires: s.charAt(i) is not '.'
+    //
+    private static int takeString(String s, int i, List<Object> acc) {
+        int b = i;
+        int n = s.length();
+        while (++i < n) {
+            char c = s.charAt(i);
+            if (c != '.' && c != '-' && c != '+' && !(c >= '0' && c <= '9'))
+                continue;
+            break;
+        }
+        acc.add(s.substring(b, i));
+        return i;
+    }
+
+    // Syntax: tok+ ( '-' tok+)? ( '+' tok+)?
+    // First token string is sequence, second is pre, third is build
+    // Tokens are separated by '.' or '-', or by changes between alpha & numeric
+    // Numeric tokens are compared as decimal integers
+    // Non-numeric tokens are compared lexicographically
+    // A version with a non-empty pre is less than a version with same seq but no pre
+    // Tokens in build may contain '-' and '+'
+    //
+    private Version(String v) {
+
+        if (v == null)
+            throw new IllegalArgumentException("Null version string");
+        int n = v.length();
+        if (n == 0)
+            throw new IllegalArgumentException("Empty version string");
+
+        int i = 0;
+        char c = v.charAt(i);
+        if (!(c >= '0' && c <= '9'))
+            throw new IllegalArgumentException(v
+                    + ": Version string does not start"
+                    + " with a number");
+
+        List<Object> sequence = new ArrayList<>(4);
+        List<Object> pre = new ArrayList<>(2);
+        List<Object> build = new ArrayList<>(2);
+
+        i = takeNumber(v, i, sequence);
+
+        while (i < n) {
+            c = v.charAt(i);
+            if (c == '.') {
+                i++;
+                continue;
+            }
+            if (c == '-' || c == '+') {
+                i++;
+                break;
+            }
+            if (c >= '0' && c <= '9')
+                i = takeNumber(v, i, sequence);
+            else
+                i = takeString(v, i, sequence);
+        }
+
+        if (c == '-' && i >= n)
+            throw new IllegalArgumentException(v + ": Empty pre-release");
+
+        while (i < n) {
+            c = v.charAt(i);
+            if (c == '.' || c == '-') {
+                i++;
+                continue;
+            }
+            if (c == '+') {
+                i++;
+                break;
+            }
+            if (c >= '0' && c <= '9')
+                i = takeNumber(v, i, pre);
+            else
+                i = takeString(v, i, pre);
+        }
+
+        if (c == '+' && i >= n)
+            throw new IllegalArgumentException(v + ": Empty pre-release");
+
+        while (i < n) {
+            c = v.charAt(i);
+            if (c == '.' || c == '-' || c == '+') {
+                i++;
+                continue;
+            }
+            if (c >= '0' && c <= '9')
+                i = takeNumber(v, i, build);
+            else
+                i = takeString(v, i, build);
+        }
+
+        this.version = v;
+        this.sequence = sequence;
+        this.pre = pre;
+        this.build = build;
+    }
+
+    /**
+     * Parses the given string as a version string.
+     *
+     * @param v The string to parse
+     * @return The resulting {@code Version}
+     * @throws IllegalArgumentException If {@code v} is {@code null}, an empty string, or cannot be
+     *                                  parsed as a version string
+     */
+    public static Version parse(String v) {
+        return new Version(v);
+    }
+
+    @SuppressWarnings("unchecked")
+    private int cmp(Object o1, Object o2) {
+        return ((Comparable) o1).compareTo(o2);
+    }
+
+    private int compareTokens(List<Object> ts1, List<Object> ts2) {
+        int n = Math.min(ts1.size(), ts2.size());
+        for (int i = 0; i < n; i++) {
+            Object o1 = ts1.get(i);
+            Object o2 = ts2.get(i);
+            if ((o1 instanceof Integer && o2 instanceof Integer)
+                    || (o1 instanceof String && o2 instanceof String)) {
+                int c = cmp(o1, o2);
+                if (c == 0)
+                    continue;
+                return c;
+            }
+            // Types differ, so convert number to string form
+            int c = o1.toString().compareTo(o2.toString());
+            if (c == 0)
+                continue;
+            return c;
+        }
+        List<Object> rest = ts1.size() > ts2.size() ? ts1 : ts2;
+        int e = rest.size();
+        for (int i = n; i < e; i++) {
+            Object o = rest.get(i);
+            if (o instanceof Integer && ((Integer) o) == 0)
+                continue;
+            return ts1.size() - ts2.size();
+        }
+        return 0;
+    }
+
+    /**
+     * Compares this module version to another module version. Module
+     * versions are compared as described in the class description.
+     *
+     * @param that The module version to compare
+     * @return A negative integer, zero, or a positive integer as this
+     * module version is less than, equal to, or greater than the
+     * given module version
+     */
+    @Override
+    public int compareTo(Version that) {
+        int c = compareTokens(this.sequence, that.sequence);
+        if (c != 0) return c;
+        if (this.pre.isEmpty()) {
+            if (!that.pre.isEmpty()) return +1;
+        } else {
+            if (that.pre.isEmpty()) return -1;
+        }
+        c = compareTokens(this.pre, that.pre);
+        if (c != 0) return c;
+        return compareTokens(this.build, that.build);
+    }
+
+    /**
+     * Tests this module version for equality with the given object.
+     *
+     * <p> If the given object is not a {@code Version} then this method
+     * returns {@code false}. Two module version are equal if their
+     * corresponding components are equal. </p>
+     *
+     * <p> This method satisfies the general contract of the {@link
+     * java.lang.Object#equals(Object) Object.equals} method. </p>
+     *
+     * @param ob the object to which this object is to be compared
+     * @return {@code true} if, and only if, the given object is a module
+     * reference that is equal to this module reference
+     */
+    @Override
+    public boolean equals(Object ob) {
+        if (!(ob instanceof Version))
+            return false;
+        return compareTo((Version) ob) == 0;
+    }
+
+    /**
+     * Computes a hash code for this module version.
+     *
+     * <p> The hash code is based upon the components of the version and
+     * satisfies the general contract of the {@link Object#hashCode
+     * Object.hashCode} method. </p>
+     *
+     * @return The hash-code value for this module version
+     */
+    @Override
+    public int hashCode() {
+        return version.hashCode();
+    }
+
+    /**
+     * Returns the string from which this version was parsed.
+     *
+     * @return The string from which this version was parsed.
+     */
+    @Override
+    public String toString() {
+        return version;
+    }
+
+}
diff --git a/app/src/main/java/org/lineageos/updater/model/UpdateBase.java b/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
index 8fcf09c..2074159 100644
--- a/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
+++ b/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
@@ -24,6 +24,7 @@
     private String mType;
     private String mVersion;
     private long mFileSize;
+    private String mUpgradeMinVersion;
 
     public UpdateBase() {
     }
@@ -36,6 +37,7 @@
         mType = update.getType();
         mVersion = update.getVersion();
         mFileSize = update.getFileSize();
+        mUpgradeMinVersion = update.getUpgradeMinVersion();
     }
 
     @Override
@@ -100,4 +102,13 @@
     public void setFileSize(long fileSize) {
         mFileSize = fileSize;
     }
+
+    @Override
+    public String getUpgradeMinVersion() {
+        return mUpgradeMinVersion;
+    }
+
+    public void setUpgradeMinVersion(String upgradeMinVersion) {
+        mUpgradeMinVersion = upgradeMinVersion;
+    }
 }
diff --git a/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java b/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
index 2041582..417d470 100644
--- a/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
+++ b/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
@@ -29,4 +29,6 @@
     String getDownloadUrl();
 
     long getFileSize();
+
+    String getUpgradeMinVersion();
 }
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c02fe1c..83b6a3c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -20,7 +20,7 @@
 
     <!-- Directory where the update files will be downloaded and stored.
          WARNING: The application can and will delete any unknown file. -->
-    <string name="download_path" translatable="false">/data/lineageos_updates/</string>
+    <string name="download_path" translatable="false">/data/ota_package/</string>
 
     <!-- Directory where the downloads will be exported to.
          The path is relative to the root of the external storage.-->
@@ -32,7 +32,7 @@
           {type} - Build type
           {incr} - Incremental version
     -->
-    <string name="updater_server_url" translatable="false">https://download.lineageos.org/api/v1/{device}/{type}/{incr}</string>
+    <string name="updater_server_url" translatable="false">https://get.leafos.org/ota/{device}/{flavor}/{incr}</string>
 
     <string name="verification_failed_notification">Verification failed</string>
     <string name="verifying_download_notification">Verifying update</string>
@@ -147,7 +147,7 @@
 
     <string name="blocked_update_dialog_title">Update blocked</string>
     <string name="blocked_update_dialog_message">This update cannot be installed using the updater app.  Please read <xliff:g id="info_url">%1$s</xliff:g> for more information.</string>
-    <string name="blocked_update_info_url" translatable="false">http://wiki.lineageos.org/devices/<xliff:g id="device_name">%1$s</xliff:g>/upgrade</string>
+    <string name="blocked_update_info_url" translatable="false">http://leafos.org/wiki/device/<xliff:g id="device_name">%1$s</xliff:g>/upgrade</string>
 
     <string name="export_channel_title">Export completion</string>
     <string name="new_updates_channel_title">New updates</string>
diff --git a/push-update.sh b/push-update.sh
index d92aacc..3383d83 100755
--- a/push-update.sh
+++ b/push-update.sh
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-updates_dir=/data/lineageos_updates
+updates_dir=/data/ota_package
 
 if [ ! -f "$1" ]; then
    echo "Usage: $0 ZIP [UNVERIFIED]"