Move app status handling to new AppUpdateStatusManager
This commit is contained in:
parent
10cbf1ef7a
commit
55c7a21c90
310
app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java
Normal file
310
app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.data.Apk;
|
||||||
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
|
import org.fdroid.fdroid.installer.ErrorDialogActivity;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AppUpdateStatusManager {
|
||||||
|
|
||||||
|
static final String BROADCAST_APPSTATUS_LIST_CHANGED = "org.fdroid.fdroid.installer.appstatus.listchange";
|
||||||
|
static final String BROADCAST_APPSTATUS_ADDED = "org.fdroid.fdroid.installer.appstatus.appchange.add";
|
||||||
|
static final String BROADCAST_APPSTATUS_CHANGED = "org.fdroid.fdroid.installer.appstatus.appchange.change";
|
||||||
|
static final String BROADCAST_APPSTATUS_REMOVED = "org.fdroid.fdroid.installer.appstatus.appchange.remove";
|
||||||
|
static final String EXTRA_APK_URL = "urlstring";
|
||||||
|
static final String EXTRA_IS_STATUS_UPDATE = "isstatusupdate";
|
||||||
|
|
||||||
|
private static final String LOGTAG = "AppUpdateStatusManager";
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
Unknown,
|
||||||
|
UpdateAvailable,
|
||||||
|
Downloading,
|
||||||
|
ReadyToInstall,
|
||||||
|
Installing,
|
||||||
|
Installed,
|
||||||
|
InstallError
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AppUpdateStatusManager getInstance(Context context) {
|
||||||
|
return new AppUpdateStatusManager(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppUpdateStatus {
|
||||||
|
final App app;
|
||||||
|
final Apk apk;
|
||||||
|
Status status;
|
||||||
|
PendingIntent intent;
|
||||||
|
int progressCurrent;
|
||||||
|
int progressMax;
|
||||||
|
String errorText;
|
||||||
|
|
||||||
|
AppUpdateStatus(App app, Apk apk, Status status, PendingIntent intent) {
|
||||||
|
this.app = app;
|
||||||
|
this.apk = apk;
|
||||||
|
this.status = status;
|
||||||
|
this.intent = intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUniqueKey() {
|
||||||
|
return apk.getUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final LocalBroadcastManager localBroadcastManager;
|
||||||
|
private static final HashMap<String, AppUpdateStatus> appMapping = new HashMap<>();
|
||||||
|
private boolean isBatchUpdating;
|
||||||
|
|
||||||
|
private AppUpdateStatusManager(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
localBroadcastManager = LocalBroadcastManager.getInstance(context.getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppUpdateStatus get(String key) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
return appMapping.get(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<AppUpdateStatus> getAll() {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
return appMapping.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setApkInternal(Apk apk, Status status, PendingIntent intent) {
|
||||||
|
if (apk == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(apk.getUrl());
|
||||||
|
if (status == null) {
|
||||||
|
// Remove
|
||||||
|
Utils.debugLog(LOGTAG, "Remove APK " + apk.apkName);
|
||||||
|
if (entry != null) {
|
||||||
|
appMapping.remove(apk.getUrl());
|
||||||
|
notifyRemove(entry);
|
||||||
|
}
|
||||||
|
} else if (entry != null) {
|
||||||
|
// Update
|
||||||
|
Utils.debugLog(LOGTAG, "Update APK " + apk.apkName + " state to " + status.name());
|
||||||
|
boolean isStatusUpdate = (entry.status != status);
|
||||||
|
entry.status = status;
|
||||||
|
entry.intent = intent;
|
||||||
|
// If intent not set, see if we need to create a default intent
|
||||||
|
if (entry.intent == null) {
|
||||||
|
entry.intent = getContentIntent(entry);
|
||||||
|
}
|
||||||
|
notifyChange(entry, isStatusUpdate);
|
||||||
|
} else {
|
||||||
|
// Add
|
||||||
|
Utils.debugLog(LOGTAG, "Add APK " + apk.apkName + " with state " + status.name());
|
||||||
|
entry = createAppEntry(apk, status, intent);
|
||||||
|
// If intent not set, see if we need to create a default intent
|
||||||
|
if (entry.intent == null) {
|
||||||
|
entry.intent = getContentIntent(entry);
|
||||||
|
}
|
||||||
|
appMapping.put(entry.getUniqueKey(), entry);
|
||||||
|
notifyAdd(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyChange() {
|
||||||
|
if (!isBatchUpdating) {
|
||||||
|
localBroadcastManager.sendBroadcast(new Intent(BROADCAST_APPSTATUS_LIST_CHANGED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyAdd(AppUpdateStatus entry) {
|
||||||
|
if (!isBatchUpdating) {
|
||||||
|
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_ADDED);
|
||||||
|
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||||
|
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyChange(AppUpdateStatus entry, boolean isStatusUpdate) {
|
||||||
|
if (!isBatchUpdating) {
|
||||||
|
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_CHANGED);
|
||||||
|
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||||
|
broadcastIntent.putExtra(EXTRA_IS_STATUS_UPDATE, isStatusUpdate);
|
||||||
|
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyRemove(AppUpdateStatus entry) {
|
||||||
|
if (!isBatchUpdating) {
|
||||||
|
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_REMOVED);
|
||||||
|
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||||
|
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppUpdateStatus createAppEntry(Apk apk, Status status, PendingIntent intent) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
App app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo);
|
||||||
|
AppUpdateStatus ret = new AppUpdateStatus(app, apk, status, intent);
|
||||||
|
appMapping.put(apk.getUrl(), ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an Apk to the AppUpdateStatusManager manager.
|
||||||
|
* @param apk The apk to add.
|
||||||
|
* @param status The current status of the app
|
||||||
|
* @param pendingIntent Action when notification is clicked. Can be null for default action(s)
|
||||||
|
*/
|
||||||
|
public void addApk(Apk apk, Status status, PendingIntent pendingIntent) {
|
||||||
|
setApkInternal(apk, status, pendingIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateApk(String key, Status status, PendingIntent pendingIntent) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(key);
|
||||||
|
if (entry != null) {
|
||||||
|
setApkInternal(entry.apk, status, pendingIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Apk getApk(String key) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(key);
|
||||||
|
if (entry != null) {
|
||||||
|
return entry.apk;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeApk(String key) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(key);
|
||||||
|
if (entry != null) {
|
||||||
|
setApkInternal(entry.apk, null, null); // remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateApkProgress(String key, int max, int current) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(key);
|
||||||
|
if (entry != null) {
|
||||||
|
entry.progressMax = max;
|
||||||
|
entry.progressCurrent = current;
|
||||||
|
notifyChange(entry, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApkError(Apk apk, String errorText) {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
AppUpdateStatus entry = appMapping.get(apk.getUrl());
|
||||||
|
if (entry == null) {
|
||||||
|
entry = createAppEntry(apk, Status.InstallError, null);
|
||||||
|
}
|
||||||
|
entry.status = Status.InstallError;
|
||||||
|
entry.errorText = errorText;
|
||||||
|
entry.intent = getAppErrorIntent(entry);
|
||||||
|
notifyChange(entry, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startBatchUpdates() {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
isBatchUpdating = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void endBatchUpdates() {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
isBatchUpdating = false;
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAllUpdates() {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
for (Iterator<Map.Entry<String, AppUpdateStatus>> it = appMapping.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
Map.Entry<String, AppUpdateStatus> entry = it.next();
|
||||||
|
if (entry.getValue().status != Status.Installed) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearAllInstalled() {
|
||||||
|
synchronized (appMapping) {
|
||||||
|
for (Iterator<Map.Entry<String, AppUpdateStatus>> it = appMapping.entrySet().iterator(); it.hasNext(); ) {
|
||||||
|
Map.Entry<String, AppUpdateStatus> entry = it.next();
|
||||||
|
if (entry.getValue().status == Status.Installed) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notifyChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent getContentIntent(AppUpdateStatus entry) {
|
||||||
|
if (entry.status == Status.UpdateAvailable) {
|
||||||
|
// Make sure we have an intent to install the app. If not set, we create an intent
|
||||||
|
// to open up the app details page for the app. From there, the user can hit "install"
|
||||||
|
return getAppDetailsIntent(entry.apk);
|
||||||
|
} else if (entry.status == Status.ReadyToInstall) {
|
||||||
|
return getAppDetailsIntent(entry.apk);
|
||||||
|
} else if (entry.status == Status.InstallError) {
|
||||||
|
return getAppErrorIntent(entry);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a {@link PendingIntent} for a {@link Notification} to send when it
|
||||||
|
* is clicked. {@link AppDetails} handles {@code Intent}s that are missing
|
||||||
|
* or bad {@link AppDetails#EXTRA_APPID}, so it does not need to be checked
|
||||||
|
* here.
|
||||||
|
*/
|
||||||
|
private PendingIntent getAppDetailsIntent(Apk apk) {
|
||||||
|
Intent notifyIntent = new Intent(context, AppDetails.class)
|
||||||
|
.putExtra(AppDetails.EXTRA_APPID, apk.packageName);
|
||||||
|
return TaskStackBuilder.create(context)
|
||||||
|
.addParentStack(AppDetails.class)
|
||||||
|
.addNextIntent(notifyIntent)
|
||||||
|
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PendingIntent getAppErrorIntent(AppUpdateStatus entry) {
|
||||||
|
String title = String.format(context.getString(R.string.install_error_notify_title), entry.app.name);
|
||||||
|
|
||||||
|
Intent errorDialogIntent = new Intent(context, ErrorDialogActivity.class);
|
||||||
|
errorDialogIntent.putExtra(
|
||||||
|
ErrorDialogActivity.EXTRA_TITLE, title);
|
||||||
|
errorDialogIntent.putExtra(
|
||||||
|
ErrorDialogActivity.EXTRA_MESSAGE, entry.errorText);
|
||||||
|
return PendingIntent.getActivity(
|
||||||
|
context,
|
||||||
|
0,
|
||||||
|
errorDialogIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
}
|
||||||
|
}
|
@ -220,8 +220,6 @@ public class FDroidApp extends Application {
|
|||||||
curTheme = Preferences.get().getTheme();
|
curTheme = Preferences.get().getTheme();
|
||||||
Preferences.get().configureProxy();
|
Preferences.get().configureProxy();
|
||||||
|
|
||||||
NotificationHelper.init(getApplicationContext());
|
|
||||||
|
|
||||||
InstalledAppProviderService.compareToPackageManager(this);
|
InstalledAppProviderService.compareToPackageManager(this);
|
||||||
|
|
||||||
// If the user changes the preference to do with filtering rooted apps,
|
// If the user changes the preference to do with filtering rooted apps,
|
||||||
@ -264,6 +262,7 @@ public class FDroidApp extends Application {
|
|||||||
|
|
||||||
CleanCacheService.schedule(this);
|
CleanCacheService.schedule(this);
|
||||||
|
|
||||||
|
NotificationHelper.create(getApplicationContext());
|
||||||
UpdateService.schedule(getApplicationContext());
|
UpdateService.schedule(getApplicationContext());
|
||||||
bluetoothAdapter = getBluetoothAdapter();
|
bluetoothAdapter = getBluetoothAdapter();
|
||||||
|
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.NotificationManagerCompat;
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.support.v4.util.LongSparseArray;
|
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
@ -22,185 +19,82 @@ import android.text.style.StyleSpan;
|
|||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.ImageSize;
|
import com.nostra13.universalimageloader.core.assist.ImageSize;
|
||||||
|
|
||||||
import org.fdroid.fdroid.data.Apk;
|
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class NotificationHelper {
|
class NotificationHelper {
|
||||||
|
|
||||||
private static final String BROADCAST_NOTIFICATIONS_UPDATES_CLEARED = "org.fdroid.fdroid.installer.notifications.updates.cleared";
|
private static final String BROADCAST_NOTIFICATIONS_ALL_UPDATES_CLEARED = "org.fdroid.fdroid.installer.notifications.allupdates.cleared";
|
||||||
|
private static final String BROADCAST_NOTIFICATIONS_ALL_INSTALLED_CLEARED = "org.fdroid.fdroid.installer.notifications.allinstalled.cleared";
|
||||||
|
private static final String BROADCAST_NOTIFICATIONS_UPDATE_CLEARED = "org.fdroid.fdroid.installer.notifications.update.cleared";
|
||||||
private static final String BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED = "org.fdroid.fdroid.installer.notifications.installed.cleared";
|
private static final String BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED = "org.fdroid.fdroid.installer.notifications.installed.cleared";
|
||||||
private static final String BROADCAST_NOTIFICATIONS_NOTIFICATION_DELETED = "org.fdroid.fdroid.installer.notifications.deleted";
|
|
||||||
|
|
||||||
private static final int NOTIFY_ID_UPDATES = 4711;
|
private static final int NOTIFY_ID_UPDATES = 1;
|
||||||
private static final int NOTIFY_ID_INSTALLED = 4712;
|
private static final int NOTIFY_ID_INSTALLED = 2;
|
||||||
|
|
||||||
private static final int MAX_UPDATES_TO_SHOW = 5;
|
private static final int MAX_UPDATES_TO_SHOW = 5;
|
||||||
private static final int MAX_INSTALLED_TO_SHOW = 10;
|
private static final int MAX_INSTALLED_TO_SHOW = 10;
|
||||||
|
|
||||||
private static final String EXTRA_NOTIFICATION_TAG = "tag";
|
private static final String EXTRA_NOTIFICATION_KEY = "key";
|
||||||
private static final String GROUP_UPDATES = "updates";
|
private static final String GROUP_UPDATES = "updates";
|
||||||
private static final String GROUP_INSTALLED = "installed";
|
private static final String GROUP_INSTALLED = "installed";
|
||||||
|
|
||||||
public enum Status {
|
private static final String LOGTAG = "NotificationHelper";
|
||||||
UpdateAvailable,
|
|
||||||
Downloading,
|
|
||||||
ReadyToInstall,
|
|
||||||
Installing,
|
|
||||||
Installed,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
|
|
||||||
private static NotificationHelper instance;
|
private static NotificationHelper instance;
|
||||||
|
|
||||||
public static void init(Context context) {
|
public static NotificationHelper create(Context context) {
|
||||||
instance = new NotificationHelper(context);
|
if (instance == null) {
|
||||||
|
instance = new NotificationHelper(context.getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static NotificationHelper getInstance() {
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AppEntry {
|
|
||||||
App app;
|
|
||||||
Apk apk;
|
|
||||||
Status status;
|
|
||||||
PendingIntent intent;
|
|
||||||
int progressCurrent;
|
|
||||||
int progressMax;
|
|
||||||
|
|
||||||
AppEntry(App app, Apk apk, Status status, PendingIntent intent) {
|
|
||||||
this.app = app;
|
|
||||||
this.apk = apk;
|
|
||||||
this.status = status;
|
|
||||||
this.intent = intent;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getTag() {
|
|
||||||
return apk.getUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
int getId() {
|
|
||||||
return getTag().hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final NotificationManagerCompat notificationManager;
|
private final NotificationManagerCompat notificationManager;
|
||||||
private HashMap<String, AppEntry> appMapping;
|
private final AppUpdateStatusManager appUpdateStatusMananger;
|
||||||
private boolean isBatchUpdating;
|
|
||||||
private ArrayList<AppEntry> updates;
|
|
||||||
private ArrayList<AppEntry> installed;
|
|
||||||
|
|
||||||
private NotificationHelper(Context context) {
|
private NotificationHelper(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
appUpdateStatusMananger = AppUpdateStatusManager.getInstance(context);
|
||||||
notificationManager = NotificationManagerCompat.from(context);
|
notificationManager = NotificationManagerCompat.from(context);
|
||||||
|
|
||||||
// We need to listen to when notifications are cleared, so that we "forget" all that we currently know about updates
|
// We need to listen to when notifications are cleared, so that we "forget" all that we currently know about updates
|
||||||
// and installs.
|
// and installs.
|
||||||
IntentFilter filter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
filter.addAction(BROADCAST_NOTIFICATIONS_UPDATES_CLEARED);
|
filter.addAction(BROADCAST_NOTIFICATIONS_ALL_UPDATES_CLEARED);
|
||||||
|
filter.addAction(BROADCAST_NOTIFICATIONS_ALL_INSTALLED_CLEARED);
|
||||||
|
filter.addAction(BROADCAST_NOTIFICATIONS_UPDATE_CLEARED);
|
||||||
filter.addAction(BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED);
|
filter.addAction(BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED);
|
||||||
filter.addAction(BROADCAST_NOTIFICATIONS_NOTIFICATION_DELETED);
|
context.registerReceiver(receiverNotificationsCleared, filter);
|
||||||
BroadcastReceiver mReceiverNotificationsCleared = new BroadcastReceiver() {
|
filter = new IntentFilter();
|
||||||
@Override
|
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED);
|
||||||
public void onReceive(Context context, Intent intent) {
|
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED);
|
||||||
switch (intent.getAction()) {
|
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED);
|
||||||
case BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED:
|
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
||||||
clearAllInstalledInternal();
|
LocalBroadcastManager.getInstance(context).registerReceiver(receiverAppStatusChanges, filter);
|
||||||
break;
|
|
||||||
case BROADCAST_NOTIFICATIONS_UPDATES_CLEARED:
|
|
||||||
clearAllUpdatesInternal();
|
|
||||||
break;
|
|
||||||
case BROADCAST_NOTIFICATIONS_NOTIFICATION_DELETED:
|
|
||||||
String id = intent.getStringExtra(EXTRA_NOTIFICATION_TAG);
|
|
||||||
// TODO
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
context.registerReceiver(mReceiverNotificationsCleared, filter);
|
|
||||||
appMapping = new HashMap<>();
|
|
||||||
updates = new ArrayList<>();
|
|
||||||
installed = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setApkInternal(Apk apk, Status status, PendingIntent intent) {
|
private boolean useStackedNotifications() {
|
||||||
if (apk == null) {
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppEntry entry = appMapping.get(apk.getUrl());
|
|
||||||
if (status == null) {
|
|
||||||
// Remove
|
|
||||||
if (entry != null) {
|
|
||||||
appMapping.remove(apk.getUrl());
|
|
||||||
notificationManager.cancel(entry.getTag(), entry.getId());
|
|
||||||
}
|
|
||||||
} else if (entry != null) {
|
|
||||||
// Update
|
|
||||||
boolean isStatusUpdate = (entry.status != status);
|
|
||||||
entry.status = status;
|
|
||||||
entry.intent = intent;
|
|
||||||
createNotificationForAppEntry(entry);
|
|
||||||
if (isStatusUpdate) {
|
|
||||||
updateSummaryNotifications();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Add
|
|
||||||
ContentResolver resolver = context.getContentResolver();
|
|
||||||
App app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo);
|
|
||||||
entry = new AppEntry(app, apk, status, intent);
|
|
||||||
appMapping.put(apk.getUrl(), entry);
|
|
||||||
createNotificationForAppEntry(entry);
|
|
||||||
updateSummaryNotifications();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setApkProgressInternal(Apk apk, int max, int current) {
|
|
||||||
if (appMapping.get(apk.getUrl()) != null) {
|
|
||||||
AppEntry entry = appMapping.get(apk.getUrl());
|
|
||||||
entry.progressMax = max;
|
|
||||||
entry.progressCurrent = current;
|
|
||||||
createNotificationForAppEntry(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearAllUpdatesInternal() {
|
|
||||||
for(Iterator<Map.Entry<String, AppEntry>> it = appMapping.entrySet().iterator(); it.hasNext(); ) {
|
|
||||||
Map.Entry<String, AppEntry> entry = it.next();
|
|
||||||
if(entry.getValue().status != Status.Installed) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearAllInstalledInternal() {
|
|
||||||
for(Iterator<Map.Entry<String, AppEntry>> it = appMapping.entrySet().iterator(); it.hasNext(); ) {
|
|
||||||
Map.Entry<String, AppEntry> entry = it.next();
|
|
||||||
if(entry.getValue().status == Status.Installed) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSummaryNotifications() {
|
private void updateSummaryNotifications() {
|
||||||
if (!isBatchUpdating) {
|
if (!notificationManager.areNotificationsEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get the list of updates and installed available
|
// Get the list of updates and installed available
|
||||||
updates.clear();
|
ArrayList<AppUpdateStatusManager.AppUpdateStatus> updates = new ArrayList<>();
|
||||||
installed.clear();
|
ArrayList<AppUpdateStatusManager.AppUpdateStatus> installed = new ArrayList<>();
|
||||||
for (Iterator<Map.Entry<String, AppEntry>> it = appMapping.entrySet().iterator(); it.hasNext(); ) {
|
for (AppUpdateStatusManager.AppUpdateStatus entry : appUpdateStatusMananger.getAll()) {
|
||||||
Map.Entry<String, AppEntry> entry = it.next();
|
if (entry.status == AppUpdateStatusManager.Status.Unknown) {
|
||||||
if (entry.getValue().status != Status.Installed) {
|
continue;
|
||||||
updates.add(entry.getValue());
|
} else if (entry.status != AppUpdateStatusManager.Status.Installed) {
|
||||||
|
updates.add(entry);
|
||||||
} else {
|
} else {
|
||||||
installed.add(entry.getValue());
|
installed.add(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,6 +102,9 @@ public class NotificationHelper {
|
|||||||
if (updates.size() == 0) {
|
if (updates.size() == 0) {
|
||||||
// No updates, remove summary
|
// No updates, remove summary
|
||||||
notificationManager.cancel(GROUP_UPDATES, NOTIFY_ID_UPDATES);
|
notificationManager.cancel(GROUP_UPDATES, NOTIFY_ID_UPDATES);
|
||||||
|
} else if (updates.size() == 1 && !useStackedNotifications()) {
|
||||||
|
// If we use stacked notifications we have already created one.
|
||||||
|
doCreateNotification(updates.get(0));
|
||||||
} else {
|
} else {
|
||||||
builder = createUpdateSummaryNotification(updates);
|
builder = createUpdateSummaryNotification(updates);
|
||||||
notificationManager.notify(GROUP_UPDATES, NOTIFY_ID_UPDATES, builder.build());
|
notificationManager.notify(GROUP_UPDATES, NOTIFY_ID_UPDATES, builder.build());
|
||||||
@ -215,85 +112,50 @@ public class NotificationHelper {
|
|||||||
if (installed.size() == 0) {
|
if (installed.size() == 0) {
|
||||||
// No installed, remove summary
|
// No installed, remove summary
|
||||||
notificationManager.cancel(GROUP_INSTALLED, NOTIFY_ID_INSTALLED);
|
notificationManager.cancel(GROUP_INSTALLED, NOTIFY_ID_INSTALLED);
|
||||||
|
} else if (installed.size() == 1 && !useStackedNotifications()) {
|
||||||
|
// If we use stacked notifications we have already created one.
|
||||||
|
doCreateNotification(installed.get(0));
|
||||||
} else {
|
} else {
|
||||||
builder = createInstalledSummaryNotification(installed);
|
builder = createInstalledSummaryNotification(installed);
|
||||||
notificationManager.notify(GROUP_INSTALLED, NOTIFY_ID_INSTALLED, builder.build());
|
notificationManager.notify(GROUP_INSTALLED, NOTIFY_ID_INSTALLED, builder.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createNotification(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||||
|
if (useStackedNotifications() && notificationManager.areNotificationsEnabled() && entry.status != AppUpdateStatusManager.Status.Unknown) {
|
||||||
|
doCreateNotification(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNotificationForAppEntry(AppEntry entry) {
|
private void doCreateNotification(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||||
NotificationCompat.Builder builder;
|
NotificationCompat.Builder builder;
|
||||||
if (entry.status == Status.Installed) {
|
int id;
|
||||||
|
if (entry.status == AppUpdateStatusManager.Status.Installed) {
|
||||||
builder = createInstalledNotification(entry);
|
builder = createInstalledNotification(entry);
|
||||||
|
id = NOTIFY_ID_INSTALLED;
|
||||||
|
notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_UPDATES);
|
||||||
} else {
|
} else {
|
||||||
builder = createUpdateNotification(entry);
|
builder = createUpdateNotification(entry);
|
||||||
|
id = NOTIFY_ID_UPDATES;
|
||||||
|
notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_INSTALLED);
|
||||||
}
|
}
|
||||||
notificationManager.notify(entry.getTag(), entry.getId(), builder.build());
|
notificationManager.notify(entry.getUniqueKey(), id, builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private NotificationCompat.Action getAction(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||||
* Add an Apk to the notifications manager.
|
if (entry.intent != null) {
|
||||||
* @param apk The apk to add.
|
if (entry.status == AppUpdateStatusManager.Status.UpdateAvailable) {
|
||||||
* @param status The current status of the app
|
return new NotificationCompat.Action(R.drawable.ic_notify_update_24dp, "Update", entry.intent);
|
||||||
* @param pendingIntent Action when notification is clicked. Can be null for default action(s)
|
} else if (entry.status == AppUpdateStatusManager.Status.Downloading || entry.status == AppUpdateStatusManager.Status.Installing) {
|
||||||
*/
|
return new NotificationCompat.Action(R.drawable.ic_notify_cancel_24dp, "Cancel", entry.intent);
|
||||||
public static void setApk(Apk apk, Status status, PendingIntent pendingIntent) {
|
} else if (entry.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
||||||
getInstance().setApkInternal(apk, status, pendingIntent);
|
return new NotificationCompat.Action(R.drawable.ic_notify_install_24dp, "Install", entry.intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void removeApk(Apk apk) {
|
|
||||||
getInstance().setApkInternal(apk, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setApkProgress(Apk apk, int max, int current) {
|
|
||||||
getInstance().setApkProgressInternal(apk, max, current);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void startBatchUpdates() {
|
|
||||||
getInstance().isBatchUpdating = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void endBatchUpdates() {
|
|
||||||
getInstance().isBatchUpdating = false;
|
|
||||||
getInstance().updateSummaryNotifications();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearAllUpdates() {
|
|
||||||
getInstance().clearAllUpdatesInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearAllInstalled() {
|
|
||||||
getInstance().clearAllInstalledInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private NotificationCompat.Action getAction(AppEntry entry) {
|
|
||||||
if (entry.status == Status.UpdateAvailable) {
|
|
||||||
// Make sure we have an intent to install the app. If not set, we create an intent
|
|
||||||
// to open up the app details page for the app. From there, the user can hit "install"
|
|
||||||
PendingIntent intent = entry.intent;
|
|
||||||
if (intent == null) {
|
|
||||||
intent = getAppDetailsIntent(0, entry.apk);
|
|
||||||
}
|
|
||||||
return new NotificationCompat.Action(R.drawable.ic_notify_update_24dp, "Update", intent);
|
|
||||||
} else if (entry.status == Status.Downloading || entry.status == Status.Installing) {
|
|
||||||
PendingIntent intent = entry.intent;
|
|
||||||
if (intent != null) {
|
|
||||||
return new NotificationCompat.Action(R.drawable.ic_notify_cancel_24dp, "Cancel", intent);
|
|
||||||
}
|
|
||||||
} else if (entry.status == Status.ReadyToInstall) {
|
|
||||||
// Make sure we have an intent to install the app. If not set, we create an intent
|
|
||||||
// to open up the app details page for the app. From there, the user can hit "install"
|
|
||||||
PendingIntent intent = entry.intent;
|
|
||||||
if (intent == null) {
|
|
||||||
intent = getAppDetailsIntent(0, entry.apk);
|
|
||||||
}
|
|
||||||
return new NotificationCompat.Action(R.drawable.ic_notify_install_24dp, "Install", intent);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSingleItemTitleString(App app, Status status) {
|
private String getSingleItemTitleString(App app, AppUpdateStatusManager.Status status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case UpdateAvailable:
|
case UpdateAvailable:
|
||||||
return "Update Available";
|
return "Update Available";
|
||||||
@ -305,11 +167,13 @@ public class NotificationHelper {
|
|||||||
return app.name;
|
return app.name;
|
||||||
case Installed:
|
case Installed:
|
||||||
return app.name;
|
return app.name;
|
||||||
|
case InstallError:
|
||||||
|
return "Install Failed";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getSingleItemContentString(App app, Status status) {
|
private String getSingleItemContentString(App app, AppUpdateStatusManager.Status status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case UpdateAvailable:
|
case UpdateAvailable:
|
||||||
return app.name;
|
return app.name;
|
||||||
@ -321,11 +185,13 @@ public class NotificationHelper {
|
|||||||
return String.format("Installing \"%s\"...", app.name);
|
return String.format("Installing \"%s\"...", app.name);
|
||||||
case Installed:
|
case Installed:
|
||||||
return "Successfully installed";
|
return "Successfully installed";
|
||||||
|
case InstallError:
|
||||||
|
return "Install Failed";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMultiItemContentString(App app, Status status) {
|
private String getMultiItemContentString(App app, AppUpdateStatusManager.Status status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case UpdateAvailable:
|
case UpdateAvailable:
|
||||||
return "Update available";
|
return "Update available";
|
||||||
@ -337,72 +203,64 @@ public class NotificationHelper {
|
|||||||
return "Installing";
|
return "Installing";
|
||||||
case Installed:
|
case Installed:
|
||||||
return "Successfully installed";
|
return "Successfully installed";
|
||||||
|
case InstallError:
|
||||||
|
return "Install Failed";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private NotificationCompat.Builder createUpdateNotification(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||||
* Get a {@link PendingIntent} for a {@link Notification} to send when it
|
|
||||||
* is clicked. {@link AppDetails} handles {@code Intent}s that are missing
|
|
||||||
* or bad {@link AppDetails#EXTRA_APPID}, so it does not need to be checked
|
|
||||||
* here.
|
|
||||||
*/
|
|
||||||
private PendingIntent getAppDetailsIntent(int requestCode, Apk apk) {
|
|
||||||
Intent notifyIntent = new Intent(context, AppDetails.class)
|
|
||||||
.putExtra(AppDetails.EXTRA_APPID, apk.packageName);
|
|
||||||
return TaskStackBuilder.create(context)
|
|
||||||
.addParentStack(AppDetails.class)
|
|
||||||
.addNextIntent(notifyIntent)
|
|
||||||
.getPendingIntent(requestCode, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private NotificationCompat.Builder createUpdateNotification(AppEntry entry) {
|
|
||||||
App app = entry.app;
|
App app = entry.app;
|
||||||
Status status = entry.status;
|
AppUpdateStatusManager.Status status = entry.status;
|
||||||
|
|
||||||
// TODO - async image loading
|
// TODO - async image loading
|
||||||
int largeIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
|
int largeIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
|
||||||
Bitmap iconLarge = ImageLoader.getInstance().loadImageSync(app.iconUrl, new ImageSize(largeIconSize, largeIconSize));
|
Bitmap iconLarge = ImageLoader.getInstance().loadImageSync(app.iconUrl, new ImageSize(largeIconSize, largeIconSize));
|
||||||
|
|
||||||
|
// TODO - why?
|
||||||
|
final int icon = Build.VERSION.SDK_INT >= 11 ? R.drawable.ic_stat_notify_updates : R.drawable.ic_launcher;
|
||||||
|
|
||||||
NotificationCompat.Builder builder =
|
NotificationCompat.Builder builder =
|
||||||
new NotificationCompat.Builder(context)
|
new NotificationCompat.Builder(context)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(false)
|
||||||
.setLargeIcon(iconLarge)
|
.setLargeIcon(iconLarge)
|
||||||
.setSmallIcon(R.drawable.ic_stat_notify_updates)
|
.setSmallIcon(icon)
|
||||||
.setContentTitle(getSingleItemTitleString(app, status))
|
.setContentTitle(getSingleItemTitleString(app, status))
|
||||||
.setContentText(getSingleItemContentString(app, status))
|
.setContentText(getSingleItemContentString(app, status))
|
||||||
.setGroup(GROUP_UPDATES);
|
.setGroup(GROUP_UPDATES);
|
||||||
|
|
||||||
|
// Handle intents
|
||||||
|
//
|
||||||
|
if (entry.intent != null) {
|
||||||
|
builder.setContentIntent(entry.intent);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle actions
|
// Handle actions
|
||||||
//
|
//
|
||||||
NotificationCompat.Action action = getAction(entry);
|
NotificationCompat.Action action = getAction(entry);
|
||||||
if (action != null) {
|
if (action != null) {
|
||||||
builder.addAction(action);
|
builder.addAction(action);
|
||||||
// TODO - also click on whole item?
|
|
||||||
builder.setContentIntent(action.getActionIntent());
|
|
||||||
} else if (entry.intent != null) {
|
|
||||||
builder.setContentIntent(entry.intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle progress bar (for some states)
|
// Handle progress bar (for some states)
|
||||||
//
|
//
|
||||||
if (status == Status.Downloading) {
|
if (status == AppUpdateStatusManager.Status.Downloading) {
|
||||||
if (entry.progressMax == 0)
|
if (entry.progressMax == 0)
|
||||||
builder.setProgress(100, 0, true);
|
builder.setProgress(100, 0, true);
|
||||||
else
|
else
|
||||||
builder.setProgress(entry.progressMax, entry.progressCurrent, false);
|
builder.setProgress(entry.progressMax, entry.progressCurrent, false);
|
||||||
} else if (status == Status.Installing) {
|
} else if (status == AppUpdateStatusManager.Status.Installing) {
|
||||||
builder.setProgress(100, 0, true); // indeterminate bar
|
builder.setProgress(100, 0, true); // indeterminate bar
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_NOTIFICATION_DELETED);
|
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_UPDATE_CLEARED);
|
||||||
intentDeleted.putExtra(EXTRA_NOTIFICATION_TAG, entry.getId());
|
intentDeleted.putExtra(EXTRA_NOTIFICATION_KEY, entry.getUniqueKey());
|
||||||
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
||||||
builder.setDeleteIntent(piDeleted);
|
builder.setDeleteIntent(piDeleted);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationCompat.Builder createUpdateSummaryNotification(ArrayList<AppEntry> updates) {
|
private NotificationCompat.Builder createUpdateSummaryNotification(ArrayList<AppUpdateStatusManager.AppUpdateStatus> updates) {
|
||||||
String title = String.format("%d Updates", updates.size());
|
String title = String.format("%d Updates", updates.size());
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
|
|
||||||
@ -410,9 +268,9 @@ public class NotificationHelper {
|
|||||||
inboxStyle.setBigContentTitle(title);
|
inboxStyle.setBigContentTitle(title);
|
||||||
|
|
||||||
for (int i = 0; i < MAX_UPDATES_TO_SHOW && i < updates.size(); i++) {
|
for (int i = 0; i < MAX_UPDATES_TO_SHOW && i < updates.size(); i++) {
|
||||||
AppEntry entry = updates.get(i);
|
AppUpdateStatusManager.AppUpdateStatus entry = updates.get(i);
|
||||||
App app = entry.app;
|
App app = entry.app;
|
||||||
Status status = entry.status;
|
AppUpdateStatusManager.Status status = entry.status;
|
||||||
|
|
||||||
String content = getMultiItemContentString(app, status);
|
String content = getMultiItemContentString(app, status);
|
||||||
SpannableStringBuilder sb = new SpannableStringBuilder(app.name);
|
SpannableStringBuilder sb = new SpannableStringBuilder(app.name);
|
||||||
@ -425,14 +283,10 @@ public class NotificationHelper {
|
|||||||
text.append(", ");
|
text.append(", ");
|
||||||
text.append(app.name);
|
text.append(app.name);
|
||||||
}
|
}
|
||||||
|
if (updates.size() > MAX_UPDATES_TO_SHOW) {
|
||||||
//if (updates.size() > MAX_UPDATES_TO_SHOW) {
|
int diff = updates.size() - MAX_UPDATES_TO_SHOW;
|
||||||
// int diff = updates.size() - MAX_UPDATES_TO_SHOW;
|
inboxStyle.setSummaryText(context.getString(R.string.update_notification_more, diff));
|
||||||
// inboxStyle.setSummaryText(context.getString(R.string.update_notification_more, diff));
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
inboxStyle.setSummaryText(title);
|
|
||||||
|
|
||||||
|
|
||||||
// Intent to open main app list
|
// Intent to open main app list
|
||||||
Intent intentObject = new Intent(context, FDroid.class);
|
Intent intentObject = new Intent(context, FDroid.class);
|
||||||
@ -445,16 +299,21 @@ public class NotificationHelper {
|
|||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setContentIntent(piAction)
|
.setContentIntent(piAction)
|
||||||
.setStyle(inboxStyle)
|
.setStyle(inboxStyle);
|
||||||
.setGroup(GROUP_UPDATES)
|
if (BuildConfig.DEBUG) {
|
||||||
|
builder.setPriority(NotificationCompat.PRIORITY_LOW); // To make not at top of list!
|
||||||
|
}
|
||||||
|
if (useStackedNotifications()) {
|
||||||
|
builder.setGroup(GROUP_UPDATES)
|
||||||
.setGroupSummary(true);
|
.setGroupSummary(true);
|
||||||
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_UPDATES_CLEARED);
|
}
|
||||||
|
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_ALL_UPDATES_CLEARED);
|
||||||
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
||||||
builder.setDeleteIntent(piDeleted);
|
builder.setDeleteIntent(piDeleted);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationCompat.Builder createInstalledNotification(AppEntry entry) {
|
private NotificationCompat.Builder createInstalledNotification(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||||
App app = entry.app;
|
App app = entry.app;
|
||||||
|
|
||||||
int largeIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
|
int largeIconSize = context.getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
|
||||||
@ -474,14 +333,14 @@ public class NotificationHelper {
|
|||||||
PendingIntent piAction = PendingIntent.getActivity(context, 0, intentObject, 0);
|
PendingIntent piAction = PendingIntent.getActivity(context, 0, intentObject, 0);
|
||||||
builder.setContentIntent(piAction);
|
builder.setContentIntent(piAction);
|
||||||
|
|
||||||
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_NOTIFICATION_DELETED);
|
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED);
|
||||||
intentDeleted.putExtra(EXTRA_NOTIFICATION_TAG, entry.getId());
|
intentDeleted.putExtra(EXTRA_NOTIFICATION_KEY, entry.getUniqueKey());
|
||||||
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
||||||
builder.setDeleteIntent(piDeleted);
|
builder.setDeleteIntent(piDeleted);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationCompat.Builder createInstalledSummaryNotification(ArrayList<AppEntry> installed) {
|
private NotificationCompat.Builder createInstalledSummaryNotification(ArrayList<AppUpdateStatusManager.AppUpdateStatus> installed) {
|
||||||
String title = String.format("%d Apps Installed", installed.size());
|
String title = String.format("%d Apps Installed", installed.size());
|
||||||
StringBuilder text = new StringBuilder();
|
StringBuilder text = new StringBuilder();
|
||||||
|
|
||||||
@ -489,7 +348,7 @@ public class NotificationHelper {
|
|||||||
bigTextStyle.setBigContentTitle(title);
|
bigTextStyle.setBigContentTitle(title);
|
||||||
|
|
||||||
for (int i = 0; i < MAX_INSTALLED_TO_SHOW && i < installed.size(); i++) {
|
for (int i = 0; i < MAX_INSTALLED_TO_SHOW && i < installed.size(); i++) {
|
||||||
AppEntry entry = installed.get(i);
|
AppUpdateStatusManager.AppUpdateStatus entry = installed.get(i);
|
||||||
App app = entry.app;
|
App app = entry.app;
|
||||||
if (text.length() > 0)
|
if (text.length() > 0)
|
||||||
text.append(", ");
|
text.append(", ");
|
||||||
@ -511,12 +370,76 @@ public class NotificationHelper {
|
|||||||
.setSmallIcon(R.drawable.ic_launcher)
|
.setSmallIcon(R.drawable.ic_launcher)
|
||||||
.setContentTitle(title)
|
.setContentTitle(title)
|
||||||
.setContentText(text)
|
.setContentText(text)
|
||||||
.setContentIntent(piAction)
|
.setContentIntent(piAction);
|
||||||
.setGroup(GROUP_INSTALLED)
|
if (useStackedNotifications()) {
|
||||||
|
builder.setGroup(GROUP_INSTALLED)
|
||||||
.setGroupSummary(true);
|
.setGroupSummary(true);
|
||||||
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED);
|
}
|
||||||
|
Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_ALL_INSTALLED_CLEARED);
|
||||||
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, 0);
|
||||||
builder.setDeleteIntent(piDeleted);
|
builder.setDeleteIntent(piDeleted);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver receiverNotificationsCleared = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case BROADCAST_NOTIFICATIONS_ALL_UPDATES_CLEARED:
|
||||||
|
appUpdateStatusMananger.clearAllUpdates();
|
||||||
|
break;
|
||||||
|
case BROADCAST_NOTIFICATIONS_ALL_INSTALLED_CLEARED:
|
||||||
|
appUpdateStatusMananger.clearAllInstalled();
|
||||||
|
break;
|
||||||
|
case BROADCAST_NOTIFICATIONS_UPDATE_CLEARED:
|
||||||
|
break;
|
||||||
|
case BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED:
|
||||||
|
String key = intent.getStringExtra(EXTRA_NOTIFICATION_KEY);
|
||||||
|
appUpdateStatusMananger.removeApk(key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private BroadcastReceiver receiverAppStatusChanges = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
switch (intent.getAction()) {
|
||||||
|
case AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED:
|
||||||
|
notificationManager.cancelAll();
|
||||||
|
for (AppUpdateStatusManager.AppUpdateStatus entry : appUpdateStatusMananger.getAll()) {
|
||||||
|
createNotification(entry);
|
||||||
|
}
|
||||||
|
updateSummaryNotifications();
|
||||||
|
break;
|
||||||
|
case AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED: {
|
||||||
|
String url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL);
|
||||||
|
AppUpdateStatusManager.AppUpdateStatus entry = appUpdateStatusMananger.get(url);
|
||||||
|
if (entry != null) {
|
||||||
|
createNotification(entry);
|
||||||
|
}
|
||||||
|
updateSummaryNotifications();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED: {
|
||||||
|
String url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL);
|
||||||
|
AppUpdateStatusManager.AppUpdateStatus entry = appUpdateStatusMananger.get(url);
|
||||||
|
if (entry != null) {
|
||||||
|
createNotification(entry);
|
||||||
|
}
|
||||||
|
if (intent.getBooleanExtra(AppUpdateStatusManager.EXTRA_IS_STATUS_UPDATE, false)) {
|
||||||
|
updateSummaryNotifications();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED: {
|
||||||
|
String url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL);
|
||||||
|
notificationManager.cancel(url, NOTIFY_ID_INSTALLED);
|
||||||
|
notificationManager.cancel(url, NOTIFY_ID_UPDATES);
|
||||||
|
updateSummaryNotifications();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,6 @@ import android.os.Process;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -79,7 +78,6 @@ public class UpdateService extends IntentService {
|
|||||||
private static final String STATE_LAST_UPDATED = "lastUpdateCheck";
|
private static final String STATE_LAST_UPDATED = "lastUpdateCheck";
|
||||||
|
|
||||||
private static final int NOTIFY_ID_UPDATING = 0;
|
private static final int NOTIFY_ID_UPDATING = 0;
|
||||||
private static final int NOTIFY_ID_UPDATES_AVAILABLE = 1;
|
|
||||||
|
|
||||||
private static final int FLAG_NET_UNAVAILABLE = 0;
|
private static final int FLAG_NET_UNAVAILABLE = 0;
|
||||||
private static final int FLAG_NET_METERED = 1;
|
private static final int FLAG_NET_METERED = 1;
|
||||||
@ -89,6 +87,7 @@ public class UpdateService extends IntentService {
|
|||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
private NotificationCompat.Builder notificationBuilder;
|
private NotificationCompat.Builder notificationBuilder;
|
||||||
|
private AppUpdateStatusManager appUpdateStatusManager;
|
||||||
|
|
||||||
public UpdateService() {
|
public UpdateService() {
|
||||||
super("UpdateService");
|
super("UpdateService");
|
||||||
@ -147,6 +146,7 @@ public class UpdateService extends IntentService {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
.setContentTitle(getString(R.string.update_notification_title));
|
.setContentTitle(getString(R.string.update_notification_title));
|
||||||
|
appUpdateStatusManager = AppUpdateStatusManager.getInstance(this);
|
||||||
|
|
||||||
// Android docs are a little sketchy, however it seems that Gingerbread is the last
|
// Android docs are a little sketchy, however it seems that Gingerbread is the last
|
||||||
// sdk that made a content intent mandatory:
|
// sdk that made a content intent mandatory:
|
||||||
@ -469,39 +469,6 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createNotificationIntent() {
|
|
||||||
Intent notifyIntent = new Intent(this, FDroid.class).putExtra(FDroid.EXTRA_TAB_UPDATE, true);
|
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder
|
|
||||||
.create(this).addParentStack(FDroid.class)
|
|
||||||
.addNextIntent(notifyIntent);
|
|
||||||
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int MAX_UPDATES_TO_SHOW = 5;
|
|
||||||
|
|
||||||
private NotificationCompat.Style createNotificationBigStyle(Cursor hasUpdates) {
|
|
||||||
|
|
||||||
final String contentText = hasUpdates.getCount() > 1
|
|
||||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
|
||||||
: getString(R.string.one_update_available);
|
|
||||||
|
|
||||||
NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();
|
|
||||||
inboxStyle.setBigContentTitle(contentText);
|
|
||||||
hasUpdates.moveToFirst();
|
|
||||||
for (int i = 0; i < Math.min(hasUpdates.getCount(), MAX_UPDATES_TO_SHOW); i++) {
|
|
||||||
App app = new App(hasUpdates);
|
|
||||||
hasUpdates.moveToNext();
|
|
||||||
inboxStyle.addLine(app.name + " (" + app.installedVersionName + " → " + app.getSuggestedVersionName() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUpdates.getCount() > MAX_UPDATES_TO_SHOW) {
|
|
||||||
int diff = hasUpdates.getCount() - MAX_UPDATES_TO_SHOW;
|
|
||||||
inboxStyle.setSummaryText(getString(R.string.update_notification_more, diff));
|
|
||||||
}
|
|
||||||
|
|
||||||
return inboxStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void autoDownloadUpdates() {
|
private void autoDownloadUpdates() {
|
||||||
Cursor cursor = getContentResolver().query(
|
Cursor cursor = getContentResolver().query(
|
||||||
AppProvider.getCanUpdateUri(),
|
AppProvider.getCanUpdateUri(),
|
||||||
@ -520,37 +487,17 @@ public class UpdateService extends IntentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showAppUpdatesNotification(Cursor hasUpdates) {
|
private void showAppUpdatesNotification(Cursor hasUpdates) {
|
||||||
|
|
||||||
if (hasUpdates != null) {
|
if (hasUpdates != null) {
|
||||||
hasUpdates.moveToFirst();
|
hasUpdates.moveToFirst();
|
||||||
NotificationHelper.startBatchUpdates();
|
appUpdateStatusManager.startBatchUpdates();
|
||||||
for (int i = 0; i < Math.min(MAX_UPDATES_TO_SHOW, hasUpdates.getCount()); i++) {
|
for (int i = 0; i < hasUpdates.getCount(); i++) {
|
||||||
App app = new App(hasUpdates);
|
App app = new App(hasUpdates);
|
||||||
hasUpdates.moveToNext();
|
hasUpdates.moveToNext();
|
||||||
Apk apk = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode);
|
Apk apk = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode);
|
||||||
NotificationHelper.setApk(apk, NotificationHelper.Status.UpdateAvailable, null);
|
appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.UpdateAvailable, null);
|
||||||
}
|
}
|
||||||
NotificationHelper.endBatchUpdates();
|
appUpdateStatusManager.endBatchUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils.debugLog(TAG, "Notifying " + hasUpdates.getCount() + " updates.");
|
|
||||||
|
|
||||||
final int icon = Build.VERSION.SDK_INT >= 11 ? R.drawable.ic_stat_notify_updates : R.drawable.ic_launcher;
|
|
||||||
|
|
||||||
final String contentText = hasUpdates.getCount() > 1
|
|
||||||
? getString(R.string.many_updates_available, hasUpdates.getCount())
|
|
||||||
: getString(R.string.one_update_available);
|
|
||||||
|
|
||||||
NotificationCompat.Builder builder =
|
|
||||||
new NotificationCompat.Builder(this)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setContentTitle(getString(R.string.fdroid_updates_available))
|
|
||||||
.setSmallIcon(icon)
|
|
||||||
.setContentIntent(createNotificationIntent())
|
|
||||||
.setContentText(contentText)
|
|
||||||
.setStyle(createNotificationBigStyle(hasUpdates));
|
|
||||||
|
|
||||||
notificationManager.notify(NOTIFY_ID_UPDATES_AVAILABLE, builder.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,44 +1,31 @@
|
|||||||
package org.fdroid.fdroid.installer;
|
package org.fdroid.fdroid.installer;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.v4.app.NotificationCompat;
|
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
|
||||||
import android.support.v4.content.IntentCompat;
|
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||||
import org.fdroid.fdroid.AppDetails;
|
import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||||
import org.fdroid.fdroid.Hasher;
|
import org.fdroid.fdroid.Hasher;
|
||||||
import org.fdroid.fdroid.NotificationHelper;
|
|
||||||
import org.fdroid.fdroid.R;
|
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
import org.fdroid.fdroid.compat.PackageManagerCompat;
|
||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
import org.fdroid.fdroid.data.AppProvider;
|
|
||||||
import org.fdroid.fdroid.data.Schema;
|
|
||||||
import org.fdroid.fdroid.net.Downloader;
|
import org.fdroid.fdroid.net.Downloader;
|
||||||
import org.fdroid.fdroid.net.DownloaderService;
|
import org.fdroid.fdroid.net.DownloaderService;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the whole process when a background update triggers an install or the user
|
* Manages the whole process when a background update triggers an install or the user
|
||||||
@ -89,16 +76,17 @@ public class InstallManagerService extends Service {
|
|||||||
* matching the {@link App}s in {@code ACTIVE_APPS}. The key is the download URL, as
|
* matching the {@link App}s in {@code ACTIVE_APPS}. The key is the download URL, as
|
||||||
* in {@link Apk#getUrl()} or {@code urlString}.
|
* in {@link Apk#getUrl()} or {@code urlString}.
|
||||||
*/
|
*/
|
||||||
private static final HashMap<String, Apk> ACTIVE_APKS = new HashMap<>(3);
|
//private static final HashMap<String, Apk> ACTIVE_APKS = new HashMap<>(3);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The collection of {@link App}s that are actively going through this whole process,
|
* The collection of {@link App}s that are actively going through this whole process,
|
||||||
* matching the {@link Apk}s in {@code ACTIVE_APKS}. The key is the
|
* matching the {@link Apk}s in {@code ACTIVE_APKS}. The key is the
|
||||||
* {@code packageName} of the app.
|
* {@code packageName} of the app.
|
||||||
*/
|
*/
|
||||||
private static final HashMap<String, App> ACTIVE_APPS = new HashMap<>(3);
|
//private static final HashMap<String, App> ACTIVE_APPS = new HashMap<>(3);
|
||||||
|
|
||||||
private LocalBroadcastManager localBroadcastManager;
|
private LocalBroadcastManager localBroadcastManager;
|
||||||
|
private AppUpdateStatusManager appUpdateStatusManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service does not use binding, so no need to implement this method
|
* This service does not use binding, so no need to implement this method
|
||||||
@ -113,18 +101,14 @@ public class InstallManagerService extends Service {
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
Utils.debugLog(TAG, "creating Service");
|
Utils.debugLog(TAG, "creating Service");
|
||||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||||
|
appUpdateStatusManager = AppUpdateStatusManager.getInstance(this);
|
||||||
|
|
||||||
BroadcastReceiver br = new BroadcastReceiver() {
|
BroadcastReceiver br = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String packageName = intent.getData().getSchemeSpecificPart();
|
String packageName = intent.getData().getSchemeSpecificPart();
|
||||||
for (Map.Entry<String, Apk> entry : ACTIVE_APKS.entrySet()) {
|
//TODO: do we need to mark as installed, or is this handled by other code already?
|
||||||
if (TextUtils.equals(packageName, entry.getValue().packageName)) {
|
//appUpdateStatusManager.removeApk(packageName);
|
||||||
String urlString = entry.getKey();
|
|
||||||
NotificationHelper.removeApk(getApkFromActive(urlString));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
IntentFilter intentFilter = new IntentFilter();
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
@ -146,10 +130,12 @@ public class InstallManagerService extends Service {
|
|||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if (ACTION_CANCEL.equals(action)) {
|
if (ACTION_CANCEL.equals(action)) {
|
||||||
DownloaderService.cancel(this, urlString);
|
DownloaderService.cancel(this, urlString);
|
||||||
Apk apk = getApkFromActive(urlString);
|
Apk apk = appUpdateStatusManager.getApk(urlString);
|
||||||
|
if (apk != null) {
|
||||||
DownloaderService.cancel(this, apk.getPatchObbUrl());
|
DownloaderService.cancel(this, apk.getPatchObbUrl());
|
||||||
DownloaderService.cancel(this, apk.getMainObbUrl());
|
DownloaderService.cancel(this, apk.getMainObbUrl());
|
||||||
NotificationHelper.removeApk(apk);
|
}
|
||||||
|
appUpdateStatusManager.removeApk(urlString);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
} else if (!ACTION_INSTALL.equals(action)) {
|
} else if (!ACTION_INSTALL.equals(action)) {
|
||||||
Utils.debugLog(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent");
|
Utils.debugLog(TAG, "Ignoring " + intent + " as it is not an " + ACTION_INSTALL + " intent");
|
||||||
@ -165,7 +151,7 @@ public class InstallManagerService extends Service {
|
|||||||
&& !DownloaderService.isQueuedOrActive(urlString)) {
|
&& !DownloaderService.isQueuedOrActive(urlString)) {
|
||||||
// TODO is there a case where we should allow an active urlString to pass through?
|
// TODO is there a case where we should allow an active urlString to pass through?
|
||||||
Utils.debugLog(TAG, urlString + " finished downloading while InstallManagerService was killed.");
|
Utils.debugLog(TAG, urlString + " finished downloading while InstallManagerService was killed.");
|
||||||
NotificationHelper.removeApk(getApkFromActive(urlString));
|
appUpdateStatusManager.removeApk(urlString);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +161,7 @@ public class InstallManagerService extends Service {
|
|||||||
Utils.debugLog(TAG, "Intent had null EXTRA_APP and/or EXTRA_APK: " + intent);
|
Utils.debugLog(TAG, "Intent had null EXTRA_APP and/or EXTRA_APK: " + intent);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
addToActive(urlString, app, apk);
|
appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.Unknown, null);
|
||||||
|
|
||||||
registerApkDownloaderReceivers(urlString);
|
registerApkDownloaderReceivers(urlString);
|
||||||
getObb(urlString, apk.getMainObbUrl(), apk.getMainObbFile(), apk.obbMainFileSha256);
|
getObb(urlString, apk.getMainObbUrl(), apk.getMainObbFile(), apk.obbMainFileSha256);
|
||||||
@ -282,32 +268,31 @@ public class InstallManagerService extends Service {
|
|||||||
intentObject.setAction(ACTION_CANCEL);
|
intentObject.setAction(ACTION_CANCEL);
|
||||||
intentObject.setData(downloadUri);
|
intentObject.setData(downloadUri);
|
||||||
PendingIntent action = PendingIntent.getService(context, 0, intentObject, 0);
|
PendingIntent action = PendingIntent.getService(context, 0, intentObject, 0);
|
||||||
NotificationHelper.setApk(getApkFromActive(urlString), NotificationHelper.Status.Downloading, action);
|
appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.Downloading, action);
|
||||||
// nothing to do
|
// nothing to do
|
||||||
break;
|
break;
|
||||||
case Downloader.ACTION_PROGRESS:
|
case Downloader.ACTION_PROGRESS:
|
||||||
int bytesRead = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
int bytesRead = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
||||||
int totalBytes = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
int totalBytes = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
||||||
NotificationHelper.setApkProgress(getApkFromActive(urlString), totalBytes, bytesRead);
|
appUpdateStatusManager.updateApkProgress(urlString, totalBytes, bytesRead);
|
||||||
break;
|
break;
|
||||||
case Downloader.ACTION_COMPLETE:
|
case Downloader.ACTION_COMPLETE:
|
||||||
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
||||||
Uri localApkUri = Uri.fromFile(localFile);
|
Uri localApkUri = Uri.fromFile(localFile);
|
||||||
|
|
||||||
Utils.debugLog(TAG, "download completed of " + urlString + " to " + localApkUri);
|
Utils.debugLog(TAG, "download completed of " + urlString + " to " + localApkUri);
|
||||||
|
appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.ReadyToInstall, null);
|
||||||
NotificationHelper.setApk(getApkFromActive(urlString), NotificationHelper.Status.ReadyToInstall, null);
|
|
||||||
|
|
||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
registerInstallerReceivers(downloadUri);
|
registerInstallerReceivers(downloadUri);
|
||||||
|
|
||||||
Apk apk = ACTIVE_APKS.get(urlString);
|
Apk apk = appUpdateStatusManager.getApk(urlString);
|
||||||
|
if (apk != null) {
|
||||||
InstallerService.install(context, localApkUri, downloadUri, apk);
|
InstallerService.install(context, localApkUri, downloadUri, apk);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Downloader.ACTION_INTERRUPTED:
|
case Downloader.ACTION_INTERRUPTED:
|
||||||
NotificationHelper.removeApk(getApkFromActive(urlString));
|
appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.UpdateAvailable, null);
|
||||||
|
|
||||||
removeFromActive(urlString);
|
|
||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -329,18 +314,18 @@ public class InstallManagerService extends Service {
|
|||||||
Apk apk;
|
Apk apk;
|
||||||
switch (intent.getAction()) {
|
switch (intent.getAction()) {
|
||||||
case Installer.ACTION_INSTALL_STARTED:
|
case Installer.ACTION_INSTALL_STARTED:
|
||||||
NotificationHelper.setApk(getApkFromActive(downloadUrl), NotificationHelper.Status.Installing, null);
|
appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.Installing, null);
|
||||||
break;
|
break;
|
||||||
case Installer.ACTION_INSTALL_COMPLETE:
|
case Installer.ACTION_INSTALL_COMPLETE:
|
||||||
NotificationHelper.setApk(getApkFromActive(downloadUrl), NotificationHelper.Status.Installed, null);
|
appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.Installed, null);
|
||||||
Apk apkComplete = removeFromActive(downloadUrl);
|
Apk apkComplete = appUpdateStatusManager.getApk(downloadUrl);
|
||||||
|
if (apkComplete != null) {
|
||||||
PackageManagerCompat.setInstaller(getPackageManager(), apkComplete.packageName);
|
PackageManagerCompat.setInstaller(getPackageManager(), apkComplete.packageName);
|
||||||
|
}
|
||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
break;
|
break;
|
||||||
case Installer.ACTION_INSTALL_INTERRUPTED:
|
case Installer.ACTION_INSTALL_INTERRUPTED:
|
||||||
NotificationHelper.setApk(getApkFromActive(downloadUrl), NotificationHelper.Status.ReadyToInstall, null);
|
appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.ReadyToInstall, null);
|
||||||
|
|
||||||
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
String errorMessage =
|
String errorMessage =
|
||||||
@ -348,21 +333,21 @@ public class InstallManagerService extends Service {
|
|||||||
|
|
||||||
// show notification if app details is not visible
|
// show notification if app details is not visible
|
||||||
if (!TextUtils.isEmpty(errorMessage)) {
|
if (!TextUtils.isEmpty(errorMessage)) {
|
||||||
App app = getAppFromActive(downloadUrl);
|
appUpdateStatusManager.setApkError(apk, errorMessage);
|
||||||
if (app == null) {
|
// App app = getAppFromActive(downloadUrl);
|
||||||
ContentResolver resolver = context.getContentResolver();
|
// if (app == null) {
|
||||||
app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo);
|
// ContentResolver resolver = context.getContentResolver();
|
||||||
}
|
// app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repo);
|
||||||
|
// }
|
||||||
// TODO - show error
|
// TODO - show error
|
||||||
}
|
}
|
||||||
removeFromActive(downloadUrl);
|
|
||||||
localBroadcastManager.unregisterReceiver(this);
|
localBroadcastManager.unregisterReceiver(this);
|
||||||
break;
|
break;
|
||||||
case Installer.ACTION_INSTALL_USER_INTERACTION:
|
case Installer.ACTION_INSTALL_USER_INTERACTION:
|
||||||
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
apk = intent.getParcelableExtra(Installer.EXTRA_APK);
|
||||||
PendingIntent installPendingIntent =
|
PendingIntent installPendingIntent =
|
||||||
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI);
|
||||||
NotificationHelper.setApk(getApkFromActive(downloadUrl), NotificationHelper.Status.ReadyToInstall, installPendingIntent);
|
appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.ReadyToInstall, installPendingIntent);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("intent action not handled!");
|
throw new RuntimeException("intent action not handled!");
|
||||||
@ -374,98 +359,6 @@ public class InstallManagerService extends Service {
|
|||||||
Installer.getInstallIntentFilter(downloadUri));
|
Installer.getInstallIntentFilter(downloadUri));
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAppName(Apk apk) {
|
|
||||||
return ACTIVE_APPS.get(apk.packageName).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void notifyError(String urlString, App app, String text) {
|
|
||||||
int downloadUrlId = urlString.hashCode();
|
|
||||||
|
|
||||||
String name;
|
|
||||||
if (app == null) {
|
|
||||||
// if we have nothing else, show the APK filename
|
|
||||||
String path = Uri.parse(urlString).getPath();
|
|
||||||
name = path.substring(path.lastIndexOf('/'), path.length());
|
|
||||||
} else {
|
|
||||||
name = app.name;
|
|
||||||
}
|
|
||||||
String title = String.format(getString(R.string.install_error_notify_title), name);
|
|
||||||
|
|
||||||
Intent errorDialogIntent = new Intent(this, ErrorDialogActivity.class);
|
|
||||||
errorDialogIntent.putExtra(
|
|
||||||
ErrorDialogActivity.EXTRA_TITLE, title);
|
|
||||||
errorDialogIntent.putExtra(
|
|
||||||
ErrorDialogActivity.EXTRA_MESSAGE, text);
|
|
||||||
PendingIntent errorDialogPendingIntent = PendingIntent.getActivity(
|
|
||||||
getApplicationContext(),
|
|
||||||
downloadUrlId,
|
|
||||||
errorDialogIntent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
|
|
||||||
NotificationCompat.Builder builder =
|
|
||||||
new NotificationCompat.Builder(this)
|
|
||||||
.setAutoCancel(true)
|
|
||||||
.setContentTitle(title)
|
|
||||||
.setContentIntent(errorDialogPendingIntent)
|
|
||||||
.setSmallIcon(R.drawable.ic_issues)
|
|
||||||
.setContentText(text);
|
|
||||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
|
||||||
nm.notify(downloadUrlId, builder.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addToActive(String urlString, App app, Apk apk) {
|
|
||||||
ACTIVE_APKS.put(urlString, apk);
|
|
||||||
ACTIVE_APPS.put(app.packageName, app);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always returns an {@link Apk} instance to avoid annoying null guards.
|
|
||||||
*/
|
|
||||||
private static Apk getApkFromActive(String urlString) {
|
|
||||||
Apk apk = ACTIVE_APKS.get(urlString);
|
|
||||||
if (apk == null) {
|
|
||||||
return new Apk();
|
|
||||||
} else {
|
|
||||||
return apk;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the {@link App} and {@Apk} instances that are associated with
|
|
||||||
* {@code urlString} from the {@link Map} of active apps. This can be
|
|
||||||
* called after this service has been destroyed and recreated based on the
|
|
||||||
* {@link BroadcastReceiver}s, in which case {@code urlString} would not
|
|
||||||
* find anything in the active maps.
|
|
||||||
*/
|
|
||||||
private static App getAppFromActive(String urlString) {
|
|
||||||
return ACTIVE_APPS.get(getApkFromActive(urlString).packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the URL from this service, and return the {@link Apk}. This returns
|
|
||||||
* an empty {@code Apk} instance if we get a null one so the code doesn't need
|
|
||||||
* lots of null guards.
|
|
||||||
*/
|
|
||||||
private static Apk removeFromActive(String urlString) {
|
|
||||||
Apk apk = ACTIVE_APKS.remove(urlString);
|
|
||||||
if (apk == null) {
|
|
||||||
return new Apk();
|
|
||||||
}
|
|
||||||
ACTIVE_APPS.remove(apk.packageName);
|
|
||||||
return apk;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PendingIntent getCancelPendingIntent(String urlString) {
|
|
||||||
Intent intent = new Intent(this, InstallManagerService.class)
|
|
||||||
.setData(Uri.parse(urlString))
|
|
||||||
.setAction(ACTION_CANCEL)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
|
|
||||||
return PendingIntent.getService(this,
|
|
||||||
urlString.hashCode(),
|
|
||||||
intent,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Install an APK, checking the cache and downloading if necessary before starting the process.
|
* Install an APK, checking the cache and downloading if necessary before starting the process.
|
||||||
* All notifications are sent as an {@link Intent} via local broadcasts to be received by
|
* All notifications are sent as an {@link Intent} via local broadcasts to be received by
|
||||||
@ -492,23 +385,4 @@ public class InstallManagerService extends Service {
|
|||||||
intent.setData(Uri.parse(urlString));
|
intent.setData(Uri.parse(urlString));
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Set} of the {@code urlString}s that are currently active.
|
|
||||||
* {@code urlString}s are used as unique IDs throughout the
|
|
||||||
* {@code InstallManagerService} process, either as a {@code String} or as an
|
|
||||||
* {@code int} from {@link String#hashCode()}.
|
|
||||||
*/
|
|
||||||
public static Set<String> getActiveDownloadUrls() {
|
|
||||||
return ACTIVE_APKS.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link Set} of the {@code packageName}s that are currently active.
|
|
||||||
* {@code packageName}s are used as unique IDs for apps throughout all of
|
|
||||||
* Android, F-Droid, and other apps stores.
|
|
||||||
*/
|
|
||||||
public static Set<String> getActivePackageNames() {
|
|
||||||
return ACTIVE_APPS.keySet();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user