diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f441156..b802ce4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3,6 +3,8 @@
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d57863f..dd5ad34 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -30,6 +30,8 @@
Download completed
Starting download
+ New updates
+
%1$s, %2$s/s
%s left
diff --git a/src/org/lineageos/updater/UpdatesCheckReceiver.java b/src/org/lineageos/updater/UpdatesCheckReceiver.java
new file mode 100644
index 0000000..6633ccd
--- /dev/null
+++ b/src/org/lineageos/updater/UpdatesCheckReceiver.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2017 The LineageOS Project
+ *
+ * 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+package org.lineageos.updater;
+
+import android.app.AlarmManager;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.support.v7.app.NotificationCompat;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.lineageos.updater.misc.Utils;
+
+import java.io.File;
+import java.io.IOException;
+
+public class UpdatesCheckReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "UpdatesCheckReceiver";
+
+ private static final String LAST_UPDATES_CHECK_PREF = "last_update_check";
+
+ private static final String DAILY_CHECK_ACTION = "daily_check_action";
+ private static final String ONESHOT_CHECK_ACTION = "oneshot_check_action";
+
+ @Override
+ public void onReceive(final Context context, Intent intent) {
+ if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ // Set a repeating alarm on boot to check for new updates once per day
+ scheduleRepeatingUpdatesCheck(context);
+ }
+
+ final SharedPreferences preferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ long lastCheck = preferences.getLong(LAST_UPDATES_CHECK_PREF, -1);
+ final long currentMillis = System.currentTimeMillis();
+ if (currentMillis > lastCheck + AlarmManager.INTERVAL_DAY) {
+ if (!Utils.isNetworkAvailable(context)) {
+ Log.d(TAG, "Network not available, scheduling new check");
+ scheduleUpdatesCheck(context);
+ return;
+ }
+
+ final File json = Utils.getCachedUpdateList(context);
+ final File jsonNew = new File(json.getAbsolutePath() + ".tmp");
+ String url = Utils.getServerURL(context);
+ DownloadClient.downloadFile(url, jsonNew, new DownloadClient.DownloadCallback() {
+ @Override
+ public void onFailure() {
+ Log.e(TAG, "Could not download updates list, scheduling new check");
+ scheduleUpdatesCheck(context);
+ }
+
+ @Override
+ public void onResponse(int statusCode, String url,
+ DownloadClient.Headers headers) {
+ }
+
+ @Override
+ public void onSuccess(String response) {
+ try {
+ if (json.exists() && Utils.checkForNewUpdates(json, jsonNew)) {
+ showNotification(context);
+ }
+ jsonNew.renameTo(json);
+ preferences.edit().putLong(LAST_UPDATES_CHECK_PREF, currentMillis).apply();
+ } catch (IOException | JSONException e) {
+ Log.e(TAG, "Could not parse list, scheduling new check", e);
+ scheduleUpdatesCheck(context);
+ }
+ }
+ });
+ }
+ }
+
+ private static void showNotification(Context context) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context);
+ notificationBuilder.setSmallIcon(R.drawable.ic_system_update);
+ Intent notificationIntent = new Intent(context, UpdatesActivity.class);
+ PendingIntent intent = PendingIntent.getActivity(context, 0, notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ notificationBuilder.setContentIntent(intent);
+ notificationBuilder.setContentTitle(context.getString(R.string.new_updates_found_title));
+ notificationManager.notify(0, notificationBuilder.build());
+ }
+
+ private static void scheduleRepeatingUpdatesCheck(Context context) {
+ AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(context, UpdatesCheckReceiver.class);
+ intent.setAction(DAILY_CHECK_ACTION);
+ PendingIntent updateCheckIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
+ alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_DAY,
+ AlarmManager.INTERVAL_DAY, updateCheckIntent);
+ }
+
+ private static void scheduleUpdatesCheck(Context context) {
+ AlarmManager alarmMgr = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(context, UpdatesCheckReceiver.class);
+ intent.setAction(ONESHOT_CHECK_ACTION);
+ PendingIntent updateCheckIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
+ alarmMgr.cancel(updateCheckIntent);
+ alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR * 2,
+ updateCheckIntent);
+ }
+}
diff --git a/src/org/lineageos/updater/misc/Utils.java b/src/org/lineageos/updater/misc/Utils.java
index 0eeff1d..5ccb303 100644
--- a/src/org/lineageos/updater/misc/Utils.java
+++ b/src/org/lineageos/updater/misc/Utils.java
@@ -16,6 +16,8 @@
package org.lineageos.updater.misc;
import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.os.SystemProperties;
import android.util.Log;
@@ -35,7 +37,9 @@ import java.io.FileReader;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public class Utils {
@@ -130,4 +134,38 @@ public class Utils {
throw new IllegalStateException("Update must be verified");
}
}
+
+ public static boolean isNetworkAvailable(Context context) {
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ NetworkInfo info = cm.getActiveNetworkInfo();
+ return !(info == null || !info.isConnected() || !info.isAvailable());
+ }
+
+ /**
+ * Compares two json formatted updates list files
+ *
+ * @param oldJson old update list
+ * @param newJson new update list
+ * @return true if newJson has at least a compatible update not available in oldJson
+ * @throws IOException
+ * @throws JSONException
+ */
+ public static boolean checkForNewUpdates(File oldJson, File newJson)
+ throws IOException, JSONException {
+ List oldList = parseJson(oldJson, true);
+ List newList = parseJson(newJson, true);
+ Set oldIds = new HashSet<>();
+ for (Update update : oldList) {
+ oldIds.add(update.getDownloadId());
+ }
+ // In case of no new updates, the old list should
+ // have all (if not more) the updates
+ for (Update update : newList) {
+ if (!oldIds.contains(update.getDownloadId())) {
+ return true;
+ }
+ }
+ return false;
+ }
}