Merge branch 'fix-notifications' into 'master'
Fix notification problems Closes #1013 See merge request !523
This commit is contained in:
commit
087e86b312
@ -28,6 +28,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
@ -35,6 +36,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
@ -65,13 +67,13 @@ import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.InstallerFactory;
|
||||
import org.fdroid.fdroid.installer.InstallerService;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.net.DownloaderService;
|
||||
import org.fdroid.fdroid.views.AppDetailsRecyclerViewAdapter;
|
||||
import org.fdroid.fdroid.views.OverscrollLinearLayoutManager;
|
||||
import org.fdroid.fdroid.views.ShareChooserDialog;
|
||||
import org.fdroid.fdroid.views.apps.FeatureImage;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog.ShareChooserDialogListener, AppDetailsRecyclerViewAdapter.AppDetailsRecyclerViewAdapterCallbacks {
|
||||
|
||||
public static final String EXTRA_APPID = "appid";
|
||||
@ -88,7 +90,7 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
private RecyclerView recyclerView;
|
||||
private AppDetailsRecyclerViewAdapter adapter;
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
private String activeDownloadUrlString;
|
||||
private AppUpdateStatusManager.AppUpdateStatus currentStatus;
|
||||
|
||||
/**
|
||||
* Check if {@code packageName} is currently visible to the user.
|
||||
@ -125,7 +127,10 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
OverscrollLinearLayoutManager lm = new OverscrollLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
|
||||
lm.setStackFromEnd(false);
|
||||
|
||||
/** The recyclerView/AppBarLayout combo has a bug that prevents a "fling" from the bottom
|
||||
// Has to be invoked after AppDetailsRecyclerViewAdapter is created.
|
||||
refreshStatus();
|
||||
|
||||
/* The recyclerView/AppBarLayout combo has a bug that prevents a "fling" from the bottom
|
||||
* to continue all the way to the top by expanding the AppBarLayout. It will instead stop
|
||||
* with the app bar in a collapsed state. See here: https://code.google.com/p/android/issues/detail?id=177729
|
||||
* Not sure this is the exact issue, but it is true that while in a fling the RecyclerView will
|
||||
@ -234,6 +239,28 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
appObserver);
|
||||
|
||||
updateNotificationsForApp();
|
||||
refreshStatus();
|
||||
registerAppStatusReceiver();
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the current install/update/download/etc status for the app we are viewing.
|
||||
* Then, asks the view to update itself to reflect this status.
|
||||
*/
|
||||
private void refreshStatus() {
|
||||
Iterator<AppUpdateStatusManager.AppUpdateStatus> statuses = AppUpdateStatusManager.getInstance(this).getByPackageName(app.packageName).iterator();
|
||||
if (statuses.hasNext()) {
|
||||
AppUpdateStatusManager.AppUpdateStatus status = statuses.next();
|
||||
updateAppStatus(status, false);
|
||||
}
|
||||
|
||||
currentStatus = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
unregisterAppStatusReceiver();
|
||||
}
|
||||
|
||||
protected void onStop() {
|
||||
@ -395,6 +422,11 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
}
|
||||
|
||||
private void initiateInstall(Apk apk) {
|
||||
if (isAppDownloading()) {
|
||||
Log.i(TAG, "Ignoring request to install " + apk.packageName + " version " + apk.versionName + ", as we are already downloading (either that or a different version).");
|
||||
return;
|
||||
}
|
||||
|
||||
Installer installer = InstallerFactory.create(this, apk);
|
||||
Intent intent = installer.getPermissionScreen();
|
||||
if (intent != null) {
|
||||
@ -408,8 +440,6 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
}
|
||||
|
||||
private void startInstall(Apk apk) {
|
||||
activeDownloadUrlString = apk.getUrl();
|
||||
registerDownloaderReceiver();
|
||||
InstallManagerService.queue(this, app, apk);
|
||||
}
|
||||
|
||||
@ -427,52 +457,81 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
localBroadcastManager.unregisterReceiver(uninstallReceiver);
|
||||
}
|
||||
|
||||
private void registerDownloaderReceiver() {
|
||||
if (activeDownloadUrlString != null) { // if a download is active
|
||||
String url = activeDownloadUrlString;
|
||||
localBroadcastManager.registerReceiver(downloadReceiver,
|
||||
DownloaderService.getIntentFilter(url));
|
||||
}
|
||||
private void registerAppStatusReceiver() {
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
||||
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED);
|
||||
filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED);
|
||||
localBroadcastManager.registerReceiver(appStatusReceiver, filter);
|
||||
}
|
||||
|
||||
private void unregisterDownloaderReceiver() {
|
||||
localBroadcastManager.unregisterReceiver(downloadReceiver);
|
||||
private void unregisterAppStatusReceiver() {
|
||||
localBroadcastManager.unregisterReceiver(appStatusReceiver);
|
||||
}
|
||||
|
||||
private void unregisterInstallReceiver() {
|
||||
localBroadcastManager.unregisterReceiver(installReceiver);
|
||||
}
|
||||
|
||||
private final BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
|
||||
private void updateAppStatus(@Nullable AppUpdateStatusManager.AppUpdateStatus newStatus, boolean justReceived) {
|
||||
this.currentStatus = newStatus;
|
||||
if (this.currentStatus == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (newStatus.status) {
|
||||
case Downloading:
|
||||
if (newStatus.progressMax == 0) {
|
||||
// The first progress notification we get telling us our status is "Downloading"
|
||||
adapter.setProgress(-1, -1, R.string.download_pending);
|
||||
} else {
|
||||
adapter.setProgress(newStatus.progressCurrent, newStatus.progressMax, 0);
|
||||
}
|
||||
break;
|
||||
|
||||
case ReadyToInstall:
|
||||
if (justReceived) {
|
||||
adapter.clearProgress();
|
||||
localBroadcastManager.registerReceiver(installReceiver, Installer.getInstallIntentFilter(Uri.parse(newStatus.getUniqueKey())));
|
||||
}
|
||||
break;
|
||||
|
||||
case DownloadInterrupted:
|
||||
if (justReceived) {
|
||||
if (TextUtils.isEmpty(newStatus.errorText)) {
|
||||
Toast.makeText(this, R.string.details_notinstalled, Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
String msg = newStatus.errorText + " " + newStatus.getUniqueKey();
|
||||
Toast.makeText(this, R.string.download_error, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
adapter.clearProgress();
|
||||
}
|
||||
break;
|
||||
|
||||
case Installing:
|
||||
case Installed:
|
||||
case UpdateAvailable:
|
||||
case InstallError:
|
||||
// Ignore.
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final BroadcastReceiver appStatusReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
switch (intent.getAction()) {
|
||||
case Downloader.ACTION_STARTED:
|
||||
adapter.setProgress(-1, -1, R.string.download_pending);
|
||||
break;
|
||||
case Downloader.ACTION_PROGRESS:
|
||||
adapter.setProgress(intent.getIntExtra(Downloader.EXTRA_BYTES_READ, -1),
|
||||
intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, -1), 0);
|
||||
break;
|
||||
case Downloader.ACTION_COMPLETE:
|
||||
// Starts the install process once the download is complete.
|
||||
cleanUpFinishedDownload();
|
||||
localBroadcastManager.registerReceiver(installReceiver,
|
||||
Installer.getInstallIntentFilter(intent.getData()));
|
||||
break;
|
||||
case Downloader.ACTION_INTERRUPTED:
|
||||
if (intent.hasExtra(Downloader.EXTRA_ERROR_MESSAGE)) {
|
||||
String msg = intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE)
|
||||
+ " " + intent.getDataString();
|
||||
Toast.makeText(context, R.string.download_error, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
} else { // user canceled
|
||||
Toast.makeText(context, R.string.details_notinstalled, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
cleanUpFinishedDownload();
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("intent action not handled!");
|
||||
AppUpdateStatusManager.AppUpdateStatus status = intent.getParcelableExtra(AppUpdateStatusManager.EXTRA_STATUS);
|
||||
|
||||
boolean isRemoving = TextUtils.equals(intent.getAction(), AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
||||
if (currentStatus != null && isRemoving && !TextUtils.equals(status.getUniqueKey(), currentStatus.getUniqueKey())) {
|
||||
Utils.debugLog(TAG, "Ignoring app status change because it belongs to " + status.getUniqueKey() + " not " + currentStatus.getUniqueKey());
|
||||
} else if (status != null && !TextUtils.equals(status.apk.packageName, app.packageName)) {
|
||||
Utils.debugLog(TAG, "Ignoring app status change because it belongs to " + status.apk.packageName + " not " + app.packageName);
|
||||
} else {
|
||||
updateAppStatus(status, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -602,8 +661,6 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
Utils.debugLog(TAG, "Getting application details for " + packageName);
|
||||
App newApp = null;
|
||||
|
||||
calcActiveDownloadUrlString(packageName);
|
||||
|
||||
if (!TextUtils.isEmpty(packageName)) {
|
||||
newApp = AppProvider.Helper.findHighestPriorityMetadata(getContentResolver(), packageName);
|
||||
}
|
||||
@ -612,25 +669,6 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
return this.app != null;
|
||||
}
|
||||
|
||||
private void calcActiveDownloadUrlString(String packageName) {
|
||||
String urlString = getPreferences(MODE_PRIVATE).getString(packageName, null);
|
||||
if (DownloaderService.isQueuedOrActive(urlString)) {
|
||||
activeDownloadUrlString = urlString;
|
||||
} else {
|
||||
// this URL is no longer active, remove it
|
||||
getPreferences(MODE_PRIVATE).edit().remove(packageName).apply();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove progress listener, suppress progress bar, set downloadHandler to null.
|
||||
*/
|
||||
private void cleanUpFinishedDownload() {
|
||||
activeDownloadUrlString = null;
|
||||
adapter.clearProgress();
|
||||
unregisterDownloaderReceiver();
|
||||
}
|
||||
|
||||
private void onAppChanged() {
|
||||
recyclerView.post(new Runnable() {
|
||||
@Override
|
||||
@ -641,6 +679,7 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
}
|
||||
AppDetailsRecyclerViewAdapter adapter = (AppDetailsRecyclerViewAdapter) recyclerView.getAdapter();
|
||||
adapter.updateItems(app);
|
||||
refreshStatus();
|
||||
supportInvalidateOptionsMenu();
|
||||
}
|
||||
});
|
||||
@ -648,7 +687,7 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
|
||||
@Override
|
||||
public boolean isAppDownloading() {
|
||||
return !TextUtils.isEmpty(activeDownloadUrlString);
|
||||
return currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Downloading;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -675,8 +714,9 @@ public class AppDetails2 extends AppCompatActivity implements ShareChooserDialog
|
||||
|
||||
@Override
|
||||
public void installCancel() {
|
||||
if (!TextUtils.isEmpty(activeDownloadUrlString)) {
|
||||
InstallManagerService.cancel(this, activeDownloadUrlString);
|
||||
if (isAppDownloading()) {
|
||||
InstallManagerService.cancel(this, currentStatus.getUniqueKey());
|
||||
adapter.clearProgress();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
@ -66,6 +68,7 @@ public final class AppUpdateStatusManager {
|
||||
public static final String BROADCAST_APPSTATUS_REMOVED = "org.fdroid.fdroid.installer.appstatus.appchange.remove";
|
||||
|
||||
public static final String EXTRA_APK_URL = "urlstring";
|
||||
public static final String EXTRA_STATUS = "status";
|
||||
|
||||
public static final String EXTRA_REASON_FOR_CHANGE = "reason";
|
||||
|
||||
@ -83,7 +86,8 @@ public final class AppUpdateStatusManager {
|
||||
private static final String LOGTAG = "AppUpdateStatusManager";
|
||||
|
||||
public enum Status {
|
||||
Unknown,
|
||||
PendingDownload,
|
||||
DownloadInterrupted,
|
||||
UpdateAvailable,
|
||||
Downloading,
|
||||
ReadyToInstall,
|
||||
@ -101,7 +105,7 @@ public final class AppUpdateStatusManager {
|
||||
|
||||
private static AppUpdateStatusManager instance;
|
||||
|
||||
public class AppUpdateStatus {
|
||||
public static class AppUpdateStatus implements Parcelable {
|
||||
public final App app;
|
||||
public final Apk apk;
|
||||
public Status status;
|
||||
@ -120,6 +124,66 @@ public final class AppUpdateStatusManager {
|
||||
public String getUniqueKey() {
|
||||
return apk.getUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps some information about the status for debugging purposes.
|
||||
*/
|
||||
public String toString() {
|
||||
return app.packageName + " [Status: " + status + ", Progress: " + progressCurrent + " / " + progressMax + "]";
|
||||
}
|
||||
|
||||
protected AppUpdateStatus(Parcel in) {
|
||||
app = in.readParcelable(getClass().getClassLoader());
|
||||
apk = in.readParcelable(getClass().getClassLoader());
|
||||
intent = in.readParcelable(getClass().getClassLoader());
|
||||
status = (Status) in.readSerializable();
|
||||
progressCurrent = in.readInt();
|
||||
progressMax = in.readInt();
|
||||
errorText = in.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeParcelable(app, 0);
|
||||
dest.writeParcelable(apk, 0);
|
||||
dest.writeParcelable(intent, 0);
|
||||
dest.writeSerializable(status);
|
||||
dest.writeInt(progressCurrent);
|
||||
dest.writeInt(progressMax);
|
||||
dest.writeString(errorText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<AppUpdateStatus> CREATOR = new Parcelable.Creator<AppUpdateStatus>() {
|
||||
@Override
|
||||
public AppUpdateStatus createFromParcel(Parcel in) {
|
||||
return new AppUpdateStatus(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppUpdateStatus[] newArray(int size) {
|
||||
return new AppUpdateStatus[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* When passing to the broadcast manager, it is important to pass a copy rather than the original object.
|
||||
* This is because if two status changes are noticed in the same event loop, than they will both refer
|
||||
* to the same status object. The objects are not parceled until the end of the event loop, and so the first
|
||||
* parceled event will refer to the updated object (with a different status) rather than the intended
|
||||
* status (i.e. the one in existence when talking to the broadcast manager).
|
||||
*/
|
||||
public AppUpdateStatus copy() {
|
||||
AppUpdateStatus copy = new AppUpdateStatus(app, apk, status, intent);
|
||||
copy.errorText = errorText;
|
||||
copy.progressCurrent = progressCurrent;
|
||||
copy.progressMax = progressMax;
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
private final Context context;
|
||||
@ -201,6 +265,7 @@ public final class AppUpdateStatusManager {
|
||||
if (!isBatchUpdating) {
|
||||
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_ADDED);
|
||||
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||
broadcastIntent.putExtra(EXTRA_STATUS, entry.copy());
|
||||
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||
}
|
||||
}
|
||||
@ -209,6 +274,7 @@ public final class AppUpdateStatusManager {
|
||||
if (!isBatchUpdating) {
|
||||
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_CHANGED);
|
||||
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||
broadcastIntent.putExtra(EXTRA_STATUS, entry.copy());
|
||||
broadcastIntent.putExtra(EXTRA_IS_STATUS_UPDATE, isStatusUpdate);
|
||||
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||
}
|
||||
@ -218,6 +284,7 @@ public final class AppUpdateStatusManager {
|
||||
if (!isBatchUpdating) {
|
||||
Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_REMOVED);
|
||||
broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey());
|
||||
broadcastIntent.putExtra(EXTRA_STATUS, entry.copy());
|
||||
localBroadcastManager.sendBroadcast(broadcastIntent);
|
||||
}
|
||||
}
|
||||
@ -316,6 +383,21 @@ public final class AppUpdateStatusManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param errorText If null, then it is likely because the user cancelled the download.
|
||||
*/
|
||||
public void setDownloadError(String url, @Nullable String errorText) {
|
||||
synchronized (appMapping) {
|
||||
AppUpdateStatus entry = appMapping.get(url);
|
||||
if (entry != null) {
|
||||
entry.status = Status.DownloadInterrupted;
|
||||
entry.errorText = errorText;
|
||||
entry.intent = null;
|
||||
notifyChange(entry, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setApkError(Apk apk, String errorText) {
|
||||
synchronized (appMapping) {
|
||||
AppUpdateStatus entry = appMapping.get(apk.getUrl());
|
||||
|
@ -148,9 +148,11 @@ class NotificationHelper {
|
||||
|
||||
private boolean shouldIgnoreEntry(AppUpdateStatusManager.AppUpdateStatus entry) {
|
||||
// Ignore unknown status
|
||||
if (entry.status == AppUpdateStatusManager.Status.Unknown) {
|
||||
if (entry.status == AppUpdateStatusManager.Status.DownloadInterrupted) {
|
||||
return true;
|
||||
} else if ((entry.status == AppUpdateStatusManager.Status.Downloading || entry.status == AppUpdateStatusManager.Status.ReadyToInstall || entry.status == AppUpdateStatusManager.Status.InstallError) &&
|
||||
} else if ((entry.status == AppUpdateStatusManager.Status.Downloading ||
|
||||
entry.status == AppUpdateStatusManager.Status.ReadyToInstall ||
|
||||
entry.status == AppUpdateStatusManager.Status.InstallError) &&
|
||||
AppDetails2.isAppVisible(entry.app.packageName)) {
|
||||
// Ignore downloading, readyToInstall and installError if we are showing the details screen for this app
|
||||
return true;
|
||||
|
@ -166,7 +166,7 @@ public class InstallManagerService extends Service {
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
|
||||
appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.Unknown, null);
|
||||
appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.Downloading, null);
|
||||
appUpdateStatusManager.markAsPendingInstall(urlString);
|
||||
|
||||
registerApkDownloaderReceivers(urlString);
|
||||
@ -270,7 +270,7 @@ public class InstallManagerService extends Service {
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case Downloader.ACTION_STARTED:
|
||||
// App should currently be in the "Unknown" state, so this changes it to "Downloading".
|
||||
// App should currently be in the "PendingDownload" state, so this changes it to "Downloading".
|
||||
Intent intentObject = new Intent(context, InstallManagerService.class);
|
||||
intentObject.setAction(ACTION_CANCEL);
|
||||
intentObject.setData(downloadUri);
|
||||
@ -299,7 +299,7 @@ public class InstallManagerService extends Service {
|
||||
break;
|
||||
case Downloader.ACTION_INTERRUPTED:
|
||||
appUpdateStatusManager.markAsNoLongerPendingInstall(urlString);
|
||||
appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.Unknown, null);
|
||||
appUpdateStatusManager.setDownloadError(urlString, intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE));
|
||||
localBroadcastManager.unregisterReceiver(this);
|
||||
break;
|
||||
default:
|
||||
|
@ -33,6 +33,7 @@ import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.fdroid.fdroid.ProgressListener;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.SanitizedFile;
|
||||
import org.fdroid.fdroid.installer.ApkCache;
|
||||
@ -200,7 +201,11 @@ public class DownloaderService extends Service {
|
||||
}
|
||||
});
|
||||
downloader.download();
|
||||
if (downloader.isNotFound()) {
|
||||
sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile, getString(R.string.download_404));
|
||||
} else {
|
||||
sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile);
|
||||
} catch (IOException e) {
|
||||
|
@ -41,13 +41,11 @@ import org.fdroid.fdroid.installer.ApkCache;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.installer.Installer;
|
||||
import org.fdroid.fdroid.installer.InstallerFactory;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.net.DownloaderService;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Iterator;
|
||||
|
||||
// TODO: Support cancelling of downloads by tapping the install button a second time.
|
||||
// TODO: Support installing of an app once downloaded by tapping the install button a second time.
|
||||
public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
|
||||
private static final String TAG = "AppListItemController";
|
||||
@ -79,15 +77,19 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
private final ImageButton cancelButton;
|
||||
|
||||
/**
|
||||
* Will operate as the "Download is complete, click to (install|update)" button.
|
||||
* Will operate as the "Download is complete, click to (install|update)" button, as well as the
|
||||
* "Installed successfully, click to run" button.
|
||||
*/
|
||||
@Nullable
|
||||
private final Button actionButton;
|
||||
|
||||
private final DisplayImageOptions displayImageOptions;
|
||||
|
||||
@Nullable
|
||||
private App currentApp;
|
||||
private String currentAppDownloadUrl;
|
||||
|
||||
@Nullable
|
||||
private AppUpdateStatusManager.AppUpdateStatus currentStatus;
|
||||
|
||||
@TargetApi(21)
|
||||
public AppListItemController(final Activity activity, View itemView) {
|
||||
@ -138,22 +140,39 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
itemView.setOnClickListener(onAppClicked);
|
||||
}
|
||||
|
||||
/**
|
||||
* Figures out the current install/update/download/etc status for the app we are viewing.
|
||||
* Then, asks the view to update itself to reflect this status.
|
||||
*/
|
||||
private void refreshStatus(@NonNull App app) {
|
||||
Iterator<AppUpdateStatusManager.AppUpdateStatus> statuses = AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName).iterator();
|
||||
if (statuses.hasNext()) {
|
||||
AppUpdateStatusManager.AppUpdateStatus status = statuses.next();
|
||||
updateAppStatus(app, status);
|
||||
} else {
|
||||
currentStatus = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void bindModel(@NonNull App app) {
|
||||
currentApp = app;
|
||||
|
||||
ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions);
|
||||
|
||||
Apk apkToInstall = ApkProvider.Helper.findApkFromAnyRepo(activity, app.packageName, app.suggestedVersionCode);
|
||||
currentAppDownloadUrl = apkToInstall.getUrl();
|
||||
refreshStatus(app);
|
||||
|
||||
final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity.getApplicationContext());
|
||||
broadcastManager.unregisterReceiver(onDownloadProgress);
|
||||
broadcastManager.unregisterReceiver(onInstallAction);
|
||||
broadcastManager.unregisterReceiver(onStatusRemoved);
|
||||
broadcastManager.unregisterReceiver(onStatusChanged);
|
||||
|
||||
broadcastManager.registerReceiver(onDownloadProgress, DownloaderService.getIntentFilter(currentAppDownloadUrl));
|
||||
broadcastManager.registerReceiver(onInstallAction, Installer.getInstallIntentFilter(Uri.parse(currentAppDownloadUrl)));
|
||||
broadcastManager.registerReceiver(onStatusRemoved, new IntentFilter(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED));
|
||||
// broadcastManager.registerReceiver(onInstallAction, Installer.getInstallIntentFilter(Uri.parse(currentAppDownloadUrl)));
|
||||
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED);
|
||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED);
|
||||
intentFilter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED);
|
||||
broadcastManager.registerReceiver(onStatusChanged, intentFilter);
|
||||
|
||||
configureAppName(app);
|
||||
configureStatusText(app);
|
||||
@ -242,34 +261,6 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the {@link AppUpdateStatusManager} to find out if there are any apks corresponding to
|
||||
* `app` which are in the process of being downloaded.
|
||||
*/
|
||||
private boolean isDownloading(@NonNull App app) {
|
||||
for (AppUpdateStatusManager.AppUpdateStatus appStatus : AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName)) {
|
||||
if (appStatus.status == AppUpdateStatusManager.Status.Downloading) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the {@link AppUpdateStatusManager} and asks if the app was just successfully installed.
|
||||
* For convenience, returns the {@link org.fdroid.fdroid.AppUpdateStatusManager.AppUpdateStatus}
|
||||
* object if it was sucessfully installed, or null otherwise.
|
||||
*/
|
||||
@Nullable
|
||||
private AppUpdateStatusManager.AppUpdateStatus wasSuccessfullyInstalled(@NonNull App app) {
|
||||
for (AppUpdateStatusManager.AppUpdateStatus appStatus : AppUpdateStatusManager.getInstance(activity).getByPackageName(app.packageName)) {
|
||||
if (appStatus.status == AppUpdateStatusManager.Status.Installed) {
|
||||
return appStatus;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The app name {@link TextView} is used for a few reasons:
|
||||
* <li> Display name + summary of the app (most common).
|
||||
@ -277,7 +268,7 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
* <li> If downloaded and ready to install, mention that it is ready to update/install.
|
||||
*/
|
||||
private void configureAppName(@NonNull App app) {
|
||||
if (isReadyToInstall(app)) {
|
||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
||||
if (app.isInstalled()) {
|
||||
String appName = activity.getString(R.string.app_list__name__downloaded_and_ready_to_update, app.name);
|
||||
if (app.lastUpdated != null) {
|
||||
@ -300,9 +291,9 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
} else {
|
||||
name.setText(activity.getString(R.string.app_list__name__downloaded_and_ready_to_install, app.name));
|
||||
}
|
||||
} else if (isDownloading(app)) {
|
||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Downloading) {
|
||||
name.setText(activity.getString(R.string.app_list__name__downloading_in_progress, app.name));
|
||||
} else if (wasSuccessfullyInstalled(app) != null) {
|
||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
|
||||
name.setText(activity.getString(R.string.app_list__name__successfully_installed, app.name));
|
||||
} else {
|
||||
name.setText(Utils.formatAppNameAndSummary(app.name, app.summary));
|
||||
@ -321,13 +312,13 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
|
||||
actionButton.setVisibility(View.VISIBLE);
|
||||
|
||||
if (wasSuccessfullyInstalled(app) != null) {
|
||||
if (activity.getPackageManager().getLaunchIntentForPackage(currentApp.packageName) != null) {
|
||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
|
||||
if (activity.getPackageManager().getLaunchIntentForPackage(app.packageName) != null) {
|
||||
actionButton.setText(R.string.menu_launch);
|
||||
} else {
|
||||
actionButton.setVisibility(View.GONE);
|
||||
}
|
||||
} else if (isReadyToInstall(app)) {
|
||||
} else if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
||||
if (app.isInstalled()) {
|
||||
actionButton.setText(R.string.app__install_downloaded_update);
|
||||
} else {
|
||||
@ -364,22 +355,6 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownloadStarted() {
|
||||
if (installButton != null) {
|
||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
|
||||
installButton.setImageLevel(0);
|
||||
}
|
||||
|
||||
if (progressBar != null) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
progressBar.setIndeterminate(true);
|
||||
}
|
||||
|
||||
if (cancelButton != null) {
|
||||
cancelButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void onDownloadProgressUpdated(int bytesRead, int totalBytes) {
|
||||
if (installButton != null) {
|
||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
|
||||
@ -416,8 +391,10 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
cancelButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (currentApp != null) {
|
||||
configureActionButton(currentApp);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final View.OnClickListener onAppClicked = new View.OnClickListener() {
|
||||
@ -443,24 +420,41 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
* Updates both the progress bar and the circular install button (which shows progress around the outside of the circle).
|
||||
* Also updates the app label to indicate that the app is being downloaded.
|
||||
*/
|
||||
private final BroadcastReceiver onDownloadProgress = new BroadcastReceiver() {
|
||||
private void updateAppStatus(@NonNull App app, @NonNull AppUpdateStatusManager.AppUpdateStatus status) {
|
||||
currentStatus = status;
|
||||
|
||||
configureAppName(app);
|
||||
configureActionButton(app);
|
||||
|
||||
switch (status.status) {
|
||||
case Downloading:
|
||||
onDownloadProgressUpdated(status.progressCurrent, status.progressMax);
|
||||
break;
|
||||
|
||||
case ReadyToInstall:
|
||||
onDownloadComplete();
|
||||
break;
|
||||
|
||||
|
||||
case Installed:
|
||||
case Installing:
|
||||
case InstallError:
|
||||
case UpdateAvailable:
|
||||
case DownloadInterrupted:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private final BroadcastReceiver onStatusChanged = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (currentApp == null || !TextUtils.equals(currentAppDownloadUrl, intent.getDataString()) || (installButton == null && progressBar == null)) {
|
||||
AppUpdateStatusManager.AppUpdateStatus newStatus = intent.getParcelableExtra(AppUpdateStatusManager.EXTRA_STATUS);
|
||||
|
||||
if (currentApp == null || !TextUtils.equals(newStatus.app.packageName, currentApp.packageName) || (installButton == null && progressBar == null)) {
|
||||
return;
|
||||
}
|
||||
|
||||
configureAppName(currentApp);
|
||||
|
||||
if (Downloader.ACTION_STARTED.equals(intent.getAction())) {
|
||||
onDownloadStarted();
|
||||
} else if (Downloader.ACTION_PROGRESS.equals(intent.getAction())) {
|
||||
int bytesRead = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
||||
int totalBytes = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
||||
onDownloadProgressUpdated(bytesRead, totalBytes);
|
||||
} else if (Downloader.ACTION_COMPLETE.equals(intent.getAction())) {
|
||||
onDownloadComplete();
|
||||
}
|
||||
updateAppStatus(currentApp, newStatus);
|
||||
}
|
||||
};
|
||||
|
||||
@ -492,26 +486,6 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If the app goes from "Successfully installed" to anything else, then reset the action button
|
||||
* and the app label text to whatever they should be.
|
||||
*/
|
||||
private final BroadcastReceiver onStatusRemoved = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (currentApp == null || currentAppDownloadUrl == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TextUtils.equals(intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL), currentAppDownloadUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
configureAppName(currentApp);
|
||||
configureActionButton(currentApp);
|
||||
}
|
||||
};
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final View.OnClickListener onActionClicked = new View.OnClickListener() {
|
||||
@Override
|
||||
@ -521,8 +495,7 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
}
|
||||
|
||||
// When the button says "Run", then launch the app.
|
||||
AppUpdateStatusManager.AppUpdateStatus successfullyInstalledStatus = wasSuccessfullyInstalled(currentApp);
|
||||
if (successfullyInstalledStatus != null) {
|
||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Installed) {
|
||||
Intent intent = activity.getPackageManager().getLaunchIntentForPackage(currentApp.packageName);
|
||||
if (intent != null) {
|
||||
activity.startActivity(intent);
|
||||
@ -530,16 +503,14 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
// Once it is explicitly launched by the user, then we can pretty much forget about
|
||||
// any sort of notification that the app was successfully installed. It should be
|
||||
// apparent to the user because they just launched it.
|
||||
AppUpdateStatusManager.getInstance(activity).removeApk(successfullyInstalledStatus.getUniqueKey());
|
||||
AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Apk suggestedApk = ApkProvider.Helper.findApkFromAnyRepo(activity, currentApp.packageName, currentApp.suggestedVersionCode);
|
||||
|
||||
if (isReadyToInstall(currentApp)) {
|
||||
File apkFilePath = ApkCache.getApkDownloadPath(activity, Uri.parse(suggestedApk.getUrl()));
|
||||
Utils.debugLog(TAG, "skip download, we have already downloaded " + suggestedApk.getUrl() + " to " + apkFilePath);
|
||||
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
|
||||
File apkFilePath = ApkCache.getApkDownloadPath(activity, Uri.parse(currentStatus.apk.getUrl()));
|
||||
Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() + " to " + apkFilePath);
|
||||
|
||||
// TODO: This seems like a bit of a hack. Is there a better way to do this by changing
|
||||
// the Installer API so that we can ask it to install without having to get it to fire
|
||||
@ -559,10 +530,11 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
}
|
||||
};
|
||||
|
||||
broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(Uri.parse(suggestedApk.getUrl())));
|
||||
Installer installer = InstallerFactory.create(activity, suggestedApk);
|
||||
installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), Uri.parse(suggestedApk.getUrl()));
|
||||
broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(Uri.parse(currentStatus.apk.getUrl())));
|
||||
Installer installer = InstallerFactory.create(activity, currentStatus.apk);
|
||||
installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), Uri.parse(currentStatus.apk.getUrl()));
|
||||
} else {
|
||||
final Apk suggestedApk = ApkProvider.Helper.findApkFromAnyRepo(activity, currentApp.packageName, currentApp.suggestedVersionCode);
|
||||
InstallManagerService.queue(activity, currentApp, suggestedApk);
|
||||
}
|
||||
}
|
||||
@ -572,11 +544,11 @@ public class AppListItemController extends RecyclerView.ViewHolder {
|
||||
private final View.OnClickListener onCancelDownload = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (currentAppDownloadUrl == null) {
|
||||
if (currentStatus == null || currentStatus.status != AppUpdateStatusManager.Status.Downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
InstallManagerService.cancel(activity, currentAppDownloadUrl);
|
||||
InstallManagerService.cancel(activity, currentStatus.getUniqueKey());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -6,15 +6,12 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates3.AdapterDelegatesManager;
|
||||
@ -23,17 +20,17 @@ import org.fdroid.fdroid.AppUpdateStatusManager;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
import org.fdroid.fdroid.views.updates.items.AppNotification;
|
||||
import org.fdroid.fdroid.views.updates.items.AppStatus;
|
||||
import org.fdroid.fdroid.views.updates.items.AppUpdateData;
|
||||
import org.fdroid.fdroid.views.updates.items.DonationPrompt;
|
||||
import org.fdroid.fdroid.views.updates.items.UpdateableApp;
|
||||
import org.fdroid.fdroid.views.updates.items.UpdateableAppsHeader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Manages the following types of information:
|
||||
@ -46,8 +43,8 @@ import java.util.List;
|
||||
* + Once "Show apps" is expanded then each app is shown along with its own download button.
|
||||
*
|
||||
* It does this by maintaining several different lists of interesting apps. Each list contains wrappers
|
||||
* around the piece of data it wants to render ({@link AppStatus}, {@link DonationPrompt},
|
||||
* {@link AppNotification}, {@link UpdateableApp}). Instead of juggling the various viewTypes
|
||||
* around the piece of data it wants to render ({@link AppStatus}, {@link UpdateableApp}).
|
||||
* Instead of juggling the various viewTypes
|
||||
* to find out which position in the adapter corresponds to which view type, this is handled by
|
||||
* the {@link UpdatesAdapter#delegatesManager}.
|
||||
*
|
||||
@ -68,12 +65,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
private final AppCompatActivity activity;
|
||||
|
||||
@Nullable
|
||||
private RecyclerView recyclerView;
|
||||
|
||||
private final List<AppStatus> appsToShowStatus = new ArrayList<>();
|
||||
private final List<DonationPrompt> appsToPromptForDonation = new ArrayList<>();
|
||||
private final List<AppNotification> appsToNotifyAbout = new ArrayList<>();
|
||||
private final List<UpdateableApp> updateableApps = new ArrayList<>();
|
||||
|
||||
private boolean showAllUpdateableApps = false;
|
||||
@ -82,8 +74,6 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
this.activity = activity;
|
||||
|
||||
delegatesManager.addDelegate(new AppStatus.Delegate(activity))
|
||||
.addDelegate(new AppNotification.Delegate())
|
||||
.addDelegate(new DonationPrompt.Delegate())
|
||||
.addDelegate(new UpdateableApp.Delegate(activity))
|
||||
.addDelegate(new UpdateableAppsHeader.Delegate(activity));
|
||||
|
||||
@ -98,8 +88,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
* {@link org.fdroid.fdroid.AppUpdateStatusManager.Status#UpdateAvailable} are not interesting here.
|
||||
*/
|
||||
private boolean shouldShowStatus(AppUpdateStatusManager.AppUpdateStatus status) {
|
||||
return status.status == AppUpdateStatusManager.Status.Unknown ||
|
||||
status.status == AppUpdateStatusManager.Status.Downloading ||
|
||||
return status.status == AppUpdateStatusManager.Status.Downloading ||
|
||||
status.status == AppUpdateStatusManager.Status.Installed ||
|
||||
status.status == AppUpdateStatusManager.Status.ReadyToInstall;
|
||||
}
|
||||
@ -135,17 +124,6 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
public void toggleAllUpdateableApps() {
|
||||
showAllUpdateableApps = !showAllUpdateableApps;
|
||||
populateItems();
|
||||
|
||||
if (showAllUpdateableApps) {
|
||||
notifyItemRangeInserted(appsToShowStatus.size() + 1, updateableApps.size());
|
||||
if (recyclerView != null) {
|
||||
// Scroll so that the "Update X apps" header is at the top of the page, and the
|
||||
// list of apps takes up the rest of the screen.
|
||||
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(appsToShowStatus.size(), 0);
|
||||
}
|
||||
} else {
|
||||
notifyItemRangeRemoved(appsToShowStatus.size() + 1, updateableApps.size());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,17 +134,29 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
private void populateItems() {
|
||||
items.clear();
|
||||
|
||||
items.addAll(appsToShowStatus);
|
||||
Set<String> toShowStatusPackageNames = new HashSet<>(appsToShowStatus.size());
|
||||
for (AppStatus app : appsToShowStatus) {
|
||||
toShowStatusPackageNames.add(app.status.app.packageName);
|
||||
items.add(app);
|
||||
}
|
||||
|
||||
if (updateableApps != null) {
|
||||
// Only count/show apps which are not shown above in the "Apps to show status" list.
|
||||
List<UpdateableApp> updateableAppsToShow = new ArrayList<>(updateableApps.size());
|
||||
for (UpdateableApp app : updateableApps) {
|
||||
if (!toShowStatusPackageNames.contains(app.app.packageName)) {
|
||||
updateableAppsToShow.add(app);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateableAppsToShow.size() > 0) {
|
||||
items.add(new UpdateableAppsHeader(activity, this, updateableAppsToShow));
|
||||
|
||||
if (updateableApps != null && updateableApps.size() > 0) {
|
||||
items.add(new UpdateableAppsHeader(activity, this, updateableApps));
|
||||
if (showAllUpdateableApps) {
|
||||
items.addAll(updateableApps);
|
||||
items.addAll(updateableAppsToShow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.addAll(appsToPromptForDonation);
|
||||
items.addAll(appsToNotifyAbout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -189,12 +179,6 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
delegatesManager.onBindViewHolder(items, position, holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
this.recyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
return new CursorLoader(
|
||||
@ -224,12 +208,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
|
||||
int numberRemoved = updateableApps.size();
|
||||
boolean hadHeader = updateableApps.size() > 0;
|
||||
boolean willHaveHeader = cursor.getCount() > 0;
|
||||
|
||||
updateableApps.clear();
|
||||
notifyItemRangeRemoved(appsToShowStatus.size(), numberRemoved + (hadHeader ? 1 : 0));
|
||||
|
||||
cursor.moveToFirst();
|
||||
while (!cursor.isAfterLast()) {
|
||||
@ -238,7 +217,7 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
}
|
||||
|
||||
populateItems();
|
||||
notifyItemRangeInserted(appsToShowStatus.size(), updateableApps.size() + (willHaveHeader ? 1 : 0));
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -294,80 +273,36 @@ public class UpdatesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
* some which are ready to install.
|
||||
*/
|
||||
private void onFoundAppsReadyToInstall() {
|
||||
if (appsToShowStatus.size() > 0) {
|
||||
int size = appsToShowStatus.size();
|
||||
appsToShowStatus.clear();
|
||||
notifyItemRangeRemoved(0, size);
|
||||
}
|
||||
|
||||
populateAppStatuses();
|
||||
notifyItemRangeInserted(0, appsToShowStatus.size());
|
||||
|
||||
if (recyclerView != null) {
|
||||
recyclerView.smoothScrollToPosition(0);
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void onAppStatusAdded(String apkUrl) {
|
||||
// We could try and find the specific place where we need to add our new item, but it is
|
||||
// far simpler to clear the list and rebuild it (sorting it in the process).
|
||||
private void onAppStatusAdded() {
|
||||
appsToShowStatus.clear();
|
||||
populateAppStatuses();
|
||||
|
||||
// After adding the new item to our list (somewhere) we can then look it back up again in
|
||||
// order to notify the recycler view and scroll to that item.
|
||||
int positionOfNewApp = -1;
|
||||
for (int i = 0; i < appsToShowStatus.size(); i++) {
|
||||
if (TextUtils.equals(appsToShowStatus.get(i).status.getUniqueKey(), apkUrl)) {
|
||||
positionOfNewApp = i;
|
||||
break;
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
if (positionOfNewApp != -1) {
|
||||
notifyItemInserted(positionOfNewApp);
|
||||
|
||||
if (recyclerView != null) {
|
||||
recyclerView.smoothScrollToPosition(positionOfNewApp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onAppStatusRemoved(String apkUrl) {
|
||||
// Find out where the item is in our internal data structure, so that we can remove it and
|
||||
// also notify the recycler view appropriately.
|
||||
int positionOfOldApp = -1;
|
||||
for (int i = 0; i < appsToShowStatus.size(); i++) {
|
||||
if (TextUtils.equals(appsToShowStatus.get(i).status.getUniqueKey(), apkUrl)) {
|
||||
positionOfOldApp = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (positionOfOldApp != -1) {
|
||||
appsToShowStatus.remove(positionOfOldApp);
|
||||
|
||||
populateItems();
|
||||
notifyItemRemoved(positionOfOldApp);
|
||||
}
|
||||
private void onAppStatusRemoved() {
|
||||
appsToShowStatus.clear();
|
||||
populateAppStatuses();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private final BroadcastReceiver receiverAppStatusChanges = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String apkUrl = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL);
|
||||
|
||||
switch (intent.getAction()) {
|
||||
case AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED:
|
||||
onManyAppStatusesChanged(intent.getStringExtra(AppUpdateStatusManager.EXTRA_REASON_FOR_CHANGE));
|
||||
break;
|
||||
|
||||
case AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED:
|
||||
onAppStatusAdded(apkUrl);
|
||||
onAppStatusAdded();
|
||||
break;
|
||||
|
||||
case AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED:
|
||||
onAppStatusRemoved(apkUrl);
|
||||
onAppStatusRemoved();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
package org.fdroid.fdroid.views.updates.items;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Each of these apps has a notification to display to the user.
|
||||
* The notification will have come from the apps metadata, provided by its maintainer. It may be
|
||||
* something about the app being removed from the repository, or perhaps security problems that
|
||||
* were identified in the app.
|
||||
*/
|
||||
public class AppNotification extends AppUpdateData {
|
||||
|
||||
public AppNotification(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public static class Delegate extends AdapterDelegate<List<AppUpdateData>> {
|
||||
|
||||
@Override
|
||||
protected boolean isForViewType(@NonNull List<AppUpdateData> items, int position) {
|
||||
return items.get(position) instanceof AppNotification;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new ViewHolder(new TextView(parent.getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position, @NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
||||
AppNotification app = (AppNotification) items.get(position);
|
||||
((ViewHolder) holder).bindApp(app);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void bindApp(AppNotification app) {
|
||||
((TextView) itemView).setText("");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package org.fdroid.fdroid.views.updates.items;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The app (if any) which we should prompt the user about potentially donating to (due to having
|
||||
* updated several times).
|
||||
*/
|
||||
public class DonationPrompt extends AppUpdateData {
|
||||
|
||||
public DonationPrompt(Activity activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
public static class Delegate extends AdapterDelegate<List<AppUpdateData>> {
|
||||
|
||||
@Override
|
||||
protected boolean isForViewType(@NonNull List<AppUpdateData> items, int position) {
|
||||
return items.get(position) instanceof DonationPrompt;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new ViewHolder(new TextView(parent.getContext()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindViewHolder(@NonNull List<AppUpdateData> items, int position, @NonNull RecyclerView.ViewHolder holder, @NonNull List<Object> payloads) {
|
||||
DonationPrompt app = (DonationPrompt) items.get(position);
|
||||
((ViewHolder) holder).bindApp(app);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
public void bindApp(DonationPrompt app) {
|
||||
((TextView) itemView).setText("");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -249,6 +249,7 @@
|
||||
- Downloaded size (human readable)
|
||||
-->
|
||||
<string name="status_download_unknown_size">Downloading\n%2$s from\n%1$s</string>
|
||||
<string name="download_404">The requested file was not found.</string>
|
||||
<string name="update_notification_title">Updating repositories</string>
|
||||
<string name="status_processing_xml_percent">Processing %2$s / %3$s (%4$d%%) from %1$s</string>
|
||||
<string name="status_connecting_to_repo">Connecting to\n%1$s</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user