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; + } }