Merge branch 'big-install-process-event-overhaul' into 'master'
Big install process event overhaul Closes #1357 See merge request fdroid/fdroidclient!717
This commit is contained in:
commit
6876088ede
@ -362,7 +362,7 @@ public class AppDetails2 extends AppCompatActivity
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
Uri uri = data.getData();
|
||||
Apk apk = ApkProvider.Helper.findByUri(this, uri, Schema.ApkTable.Cols.ALL);
|
||||
startInstall(apk);
|
||||
InstallManagerService.queue(this, app, apk);
|
||||
}
|
||||
break;
|
||||
case REQUEST_UNINSTALL_DIALOG:
|
||||
@ -373,6 +373,12 @@ public class AppDetails2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installApk() {
|
||||
Apk apkToInstall = ApkProvider.Helper.findSuggestedApk(this, app);
|
||||
installApk(apkToInstall);
|
||||
}
|
||||
|
||||
// Install the version of this app denoted by 'app.curApk'.
|
||||
@Override
|
||||
public void installApk(final Apk apk) {
|
||||
@ -426,12 +432,6 @@ public class AppDetails2 extends AppCompatActivity
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -441,10 +441,6 @@ public class AppDetails2 extends AppCompatActivity
|
||||
return;
|
||||
}
|
||||
|
||||
startInstall(apk);
|
||||
}
|
||||
|
||||
private void startInstall(Apk apk) {
|
||||
InstallManagerService.queue(this, app, apk);
|
||||
}
|
||||
|
||||
@ -485,6 +481,7 @@ public class AppDetails2 extends AppCompatActivity
|
||||
}
|
||||
|
||||
switch (newStatus.status) {
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
if (newStatus.progressMax == 0) {
|
||||
// The first progress notification we get telling us our status is "Downloading"
|
||||
@ -708,11 +705,6 @@ public class AppDetails2 extends AppCompatActivity
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppDownloading() {
|
||||
return currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.Downloading;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAndroidBeam() {
|
||||
NfcHelper.setAndroidBeam(this, app.packageName);
|
||||
@ -737,7 +729,7 @@ public class AppDetails2 extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public void installCancel() {
|
||||
if (isAppDownloading()) {
|
||||
if (currentStatus != null) {
|
||||
InstallManagerService.cancel(this, currentStatus.getUniqueKey());
|
||||
}
|
||||
}
|
||||
@ -753,18 +745,6 @@ public class AppDetails2 extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installApk() {
|
||||
Apk apkToInstall = ApkProvider.Helper.findSuggestedApk(this, app);
|
||||
installApk(apkToInstall);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgradeApk() {
|
||||
Apk apkToInstall = ApkProvider.Helper.findSuggestedApk(this, app);
|
||||
installApk(apkToInstall);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall the app from the current screen. Since there are many ways
|
||||
* to uninstall an app, including from Google Play, {@code adb uninstall},
|
||||
|
@ -91,13 +91,14 @@ public final class AppUpdateStatusManager {
|
||||
private static final String LOGTAG = "AppUpdateStatusManager";
|
||||
|
||||
public enum Status {
|
||||
PendingInstall,
|
||||
DownloadInterrupted,
|
||||
UpdateAvailable,
|
||||
Downloading,
|
||||
ReadyToInstall,
|
||||
Installing,
|
||||
Installed,
|
||||
InstallError
|
||||
InstallError,
|
||||
}
|
||||
|
||||
public static AppUpdateStatusManager getInstance(Context context) {
|
||||
@ -426,6 +427,7 @@ public final class AppUpdateStatusManager {
|
||||
entry.errorText = errorText;
|
||||
entry.intent = null;
|
||||
notifyChange(entry, true);
|
||||
removeApk(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -230,6 +230,7 @@ class NotificationHelper {
|
||||
case UpdateAvailable:
|
||||
return new NotificationCompat.Action(R.drawable.ic_file_download, context.getString(R.string.notification_action_update), entry.intent);
|
||||
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
case Installing:
|
||||
return new NotificationCompat.Action(R.drawable.ic_cancel, context.getString(R.string.notification_action_cancel), entry.intent);
|
||||
@ -245,6 +246,7 @@ class NotificationHelper {
|
||||
switch (status) {
|
||||
case UpdateAvailable:
|
||||
return context.getString(R.string.notification_title_single_update_available);
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
return app.name;
|
||||
case ReadyToInstall:
|
||||
@ -263,6 +265,7 @@ class NotificationHelper {
|
||||
switch (status) {
|
||||
case UpdateAvailable:
|
||||
return app.name;
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
return context.getString(app.isInstalled(context) ? R.string.notification_content_single_downloading_update : R.string.notification_content_single_downloading, app.name);
|
||||
case ReadyToInstall:
|
||||
@ -281,6 +284,7 @@ class NotificationHelper {
|
||||
switch (status) {
|
||||
case UpdateAvailable:
|
||||
return context.getString(R.string.notification_title_summary_update_available);
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
return context.getString(app.isInstalled(context) ? R.string.notification_title_summary_downloading_update : R.string.notification_title_summary_downloading);
|
||||
case ReadyToInstall:
|
||||
|
@ -145,7 +145,6 @@ public class FileInstallerActivity extends FragmentActivity {
|
||||
|
||||
private void installPackage(Uri localApkUri, Uri downloadUri, Apk apk) {
|
||||
Utils.debugLog(TAG, "Installing: " + localApkUri.getPath());
|
||||
installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_STARTED);
|
||||
File path = apk.getMediaInstallPath(activity.getApplicationContext());
|
||||
path.mkdirs();
|
||||
try {
|
||||
|
@ -34,7 +34,13 @@ import java.io.IOException;
|
||||
/**
|
||||
* Manages the whole process when a background update triggers an install or the user
|
||||
* requests an APK to be installed. It handles checking whether the APK is cached,
|
||||
* downloading it, putting up and maintaining a {@link Notification}, and more.
|
||||
* downloading it, putting up and maintaining a {@link Notification}, and more. This
|
||||
* {@code Service} tracks packages that are in the process as "Pending Installs".
|
||||
* Then {@link DownloaderService} and {@link InstallerService} individually track
|
||||
* packages for those phases of the whole install process. Each of those
|
||||
* {@code Services} have their own related events. For tracking status during the
|
||||
* whole process, {@link AppUpdateStatusManager} tracks the status as represented by
|
||||
* {@link AppUpdateStatusManager.AppUpdateStatus}.
|
||||
* <p>
|
||||
* The {@link App} and {@link Apk} instances are sent via
|
||||
* {@link Intent#putExtra(String, android.os.Bundle)}
|
||||
@ -103,7 +109,6 @@ public class InstallManagerService extends Service {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
Utils.debugLog(TAG, "creating Service");
|
||||
localBroadcastManager = LocalBroadcastManager.getInstance(this);
|
||||
appUpdateStatusManager = AppUpdateStatusManager.getInstance(this);
|
||||
|
||||
@ -171,7 +176,6 @@ public class InstallManagerService extends Service {
|
||||
DownloaderService.cancel(this, apk.getPatchObbUrl());
|
||||
DownloaderService.cancel(this, apk.getMainObbUrl());
|
||||
}
|
||||
appUpdateStatusManager.removeApk(urlString);
|
||||
return START_NOT_STICKY;
|
||||
} else if (ACTION_INSTALL.equals(action)) {
|
||||
if (!isPendingInstall(urlString)) {
|
||||
@ -455,14 +459,12 @@ public class InstallManagerService extends Service {
|
||||
*/
|
||||
public static void queue(Context context, App app, @NonNull Apk apk) {
|
||||
String urlString = apk.getUrl();
|
||||
AppUpdateStatusManager.getInstance(context).addApk(apk, AppUpdateStatusManager.Status.PendingInstall, null);
|
||||
putPendingInstall(context, urlString, apk.packageName);
|
||||
Uri downloadUri = Uri.parse(urlString);
|
||||
Installer.sendBroadcastInstall(context, downloadUri, Installer.ACTION_INSTALL_STARTED, apk,
|
||||
null, null);
|
||||
Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString);
|
||||
Intent intent = new Intent(context, InstallManagerService.class);
|
||||
intent.setAction(ACTION_INSTALL);
|
||||
intent.setData(downloadUri);
|
||||
intent.setData(Uri.parse(urlString));
|
||||
intent.putExtra(EXTRA_APP, app);
|
||||
intent.putExtra(EXTRA_APK, apk);
|
||||
context.startService(intent);
|
||||
@ -487,12 +489,25 @@ public class InstallManagerService extends Service {
|
||||
return pendingInstalls.contains(urlString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up by {@code packageName} whether it is a Pending Install.
|
||||
*
|
||||
* @see #isPendingInstall(String)
|
||||
*/
|
||||
public static boolean isPendingInstall(Context context, String packageName) {
|
||||
if (pendingInstalls == null) {
|
||||
pendingInstalls = getPendingInstalls(context);
|
||||
}
|
||||
return pendingInstalls.getAll().values().contains(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a given APK as in the process of being installed, with
|
||||
* the {@code urlString} of the download used as the unique ID,
|
||||
* and the file hash used to verify that things are the same.
|
||||
*
|
||||
* @see #isPendingInstall(String)
|
||||
* @see #isPendingInstall(Context, String)
|
||||
*/
|
||||
public static void putPendingInstall(Context context, String urlString, String packageName) {
|
||||
if (pendingInstalls == null) {
|
||||
|
@ -115,6 +115,8 @@ public class InstallerService extends JobIntentService {
|
||||
* @see #uninstall(Context, Apk)
|
||||
*/
|
||||
public static void install(Context context, Uri localApkUri, Uri downloadUri, Apk apk) {
|
||||
Installer.sendBroadcastInstall(context, downloadUri, Installer.ACTION_INSTALL_STARTED, apk,
|
||||
null, null);
|
||||
Intent intent = new Intent(context, InstallerService.class);
|
||||
intent.setAction(ACTION_INSTALL);
|
||||
intent.setData(localApkUri);
|
||||
|
@ -43,6 +43,7 @@ import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
||||
import org.fdroid.fdroid.data.RepoProvider;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
import org.fdroid.fdroid.privileged.views.AppDiff;
|
||||
import org.fdroid.fdroid.privileged.views.AppSecurityPermissions;
|
||||
import org.fdroid.fdroid.views.main.MainActivity;
|
||||
@ -57,8 +58,6 @@ public class AppDetailsRecyclerViewAdapter
|
||||
|
||||
public interface AppDetailsRecyclerViewAdapterCallbacks {
|
||||
|
||||
boolean isAppDownloading();
|
||||
|
||||
void enableAndroidBeam();
|
||||
|
||||
void disableAndroidBeam();
|
||||
@ -69,8 +68,6 @@ public class AppDetailsRecyclerViewAdapter
|
||||
|
||||
void installApk(Apk apk);
|
||||
|
||||
void upgradeApk();
|
||||
|
||||
void uninstallApk();
|
||||
|
||||
void installCancel();
|
||||
@ -378,11 +375,17 @@ public class AppDetailsRecyclerViewAdapter
|
||||
progressLabel.setText(resIdString);
|
||||
progressLabel.setContentDescription(context.getString(R.string.downloading));
|
||||
progressPercent.setText("");
|
||||
if (resIdString == R.string.installing || resIdString == R.string.uninstalling) {
|
||||
progressCancel.setVisibility(View.GONE);
|
||||
} else {
|
||||
progressCancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setProgress(long bytesDownloaded, long totalBytes) {
|
||||
progressLayout.setVisibility(View.VISIBLE);
|
||||
buttonLayout.setVisibility(View.GONE);
|
||||
progressCancel.setVisibility(View.VISIBLE);
|
||||
|
||||
progressBar.setMax(Utils.bytesToKb(totalBytes));
|
||||
progressBar.setProgress(Utils.bytesToKb(bytesDownloaded));
|
||||
@ -484,9 +487,11 @@ public class AppDetailsRecyclerViewAdapter
|
||||
buttonSecondaryView.setOnClickListener(onUnInstallClickListener);
|
||||
buttonPrimaryView.setText(R.string.menu_install);
|
||||
buttonPrimaryView.setVisibility(versions.size() > 0 ? View.VISIBLE : View.GONE);
|
||||
if (callbacks.isAppDownloading()) {
|
||||
if (InstallManagerService.isPendingInstall(context, app.packageName)) {
|
||||
buttonPrimaryView.setText(R.string.downloading);
|
||||
buttonPrimaryView.setEnabled(false);
|
||||
buttonLayout.setVisibility(View.GONE);
|
||||
progressLayout.setVisibility(View.VISIBLE);
|
||||
} else if (!app.isInstalled(context) && suggestedApk != null) {
|
||||
// Check count > 0 due to incompatible apps resulting in an empty list.
|
||||
callbacks.disableAndroidBeam();
|
||||
@ -494,6 +499,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
buttonPrimaryView.setText(R.string.menu_install);
|
||||
buttonPrimaryView.setOnClickListener(onInstallClickListener);
|
||||
buttonPrimaryView.setEnabled(true);
|
||||
buttonLayout.setVisibility(View.VISIBLE);
|
||||
progressLayout.setVisibility(View.GONE);
|
||||
} else if (app.isInstalled(context)) {
|
||||
callbacks.enableAndroidBeam();
|
||||
if (app.canAndWantToUpdate(context) && suggestedApk != null) {
|
||||
@ -508,10 +515,8 @@ public class AppDetailsRecyclerViewAdapter
|
||||
}
|
||||
}
|
||||
buttonPrimaryView.setEnabled(true);
|
||||
}
|
||||
if (callbacks.isAppDownloading()) {
|
||||
buttonLayout.setVisibility(View.GONE);
|
||||
progressLayout.setVisibility(View.VISIBLE);
|
||||
buttonLayout.setVisibility(View.VISIBLE);
|
||||
progressLayout.setVisibility(View.GONE);
|
||||
} else {
|
||||
buttonLayout.setVisibility(View.VISIBLE);
|
||||
progressLayout.setVisibility(View.GONE);
|
||||
@ -1097,7 +1102,7 @@ public class AppDetailsRecyclerViewAdapter
|
||||
private final View.OnClickListener onUpgradeClickListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
callbacks.upgradeApk();
|
||||
callbacks.installApk();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -56,7 +56,7 @@ import java.util.Iterator;
|
||||
* </ul>
|
||||
* <p>
|
||||
* The state of the UI is defined in a dumb {@link AppListItemState} class, then applied to the UI
|
||||
* in the {@link #refreshView(App, AppUpdateStatus)} method.
|
||||
* in the {@link #updateAppStatus(App, AppUpdateStatus)} method.
|
||||
*/
|
||||
public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
|
||||
@ -220,15 +220,10 @@ public abstract 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 void updateAppStatus(@NonNull App app, @Nullable AppUpdateStatus status) {
|
||||
currentStatus = status;
|
||||
refreshView(app, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* Queries the current state via {@link #getCurrentViewState(App, AppUpdateStatus)}
|
||||
* and then updates the relevant widgets depending on that state.
|
||||
* <p>
|
||||
@ -238,7 +233,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
* @see AppListItemState
|
||||
* @see #getCurrentViewState(App, AppUpdateStatus)
|
||||
*/
|
||||
private void refreshView(@NonNull App app, @Nullable AppUpdateStatus appStatus) {
|
||||
private void updateAppStatus(@NonNull App app, @Nullable AppUpdateStatus appStatus) {
|
||||
currentStatus = appStatus;
|
||||
|
||||
AppListItemState viewState = getCurrentViewState(app, appStatus);
|
||||
|
||||
@ -289,12 +285,14 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
if (viewState.shouldShowActionButton()) {
|
||||
installButton.setVisibility(View.GONE);
|
||||
} else if (viewState.showProgress()) {
|
||||
installButton.setEnabled(false);
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download_progress));
|
||||
int progressAsDegrees = viewState.getProgressMax() <= 0 ? 0 :
|
||||
(int) (((float) viewState.getProgressCurrent() / viewState.getProgressMax()) * 360);
|
||||
installButton.setImageLevel(progressAsDegrees);
|
||||
} else if (viewState.shouldShowInstall()) {
|
||||
installButton.setEnabled(true);
|
||||
installButton.setVisibility(View.VISIBLE);
|
||||
installButton.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.ic_download));
|
||||
} else {
|
||||
@ -332,9 +330,13 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
case ReadyToInstall:
|
||||
return getViewStateReadyToInstall(app);
|
||||
|
||||
case PendingInstall:
|
||||
case Downloading:
|
||||
return getViewStateDownloading(app, appStatus);
|
||||
|
||||
case Installing:
|
||||
return getViewStateInstalling(app);
|
||||
|
||||
case Installed:
|
||||
return getViewStateInstalled(app);
|
||||
|
||||
@ -344,6 +346,16 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
|
||||
}
|
||||
}
|
||||
|
||||
protected AppListItemState getViewStateInstalling(@NonNull App app) {
|
||||
CharSequence mainText = activity.getString(
|
||||
R.string.app_list__name__downloading_in_progress, app.name);
|
||||
|
||||
return new AppListItemState(app)
|
||||
.setMainText(mainText)
|
||||
.showActionButton(null)
|
||||
.setStatusText(activity.getString(R.string.notification_content_single_installing, app.name));
|
||||
}
|
||||
|
||||
protected AppListItemState getViewStateInstalled(@NonNull App app) {
|
||||
CharSequence mainText = activity.getString(
|
||||
R.string.app_list__name__successfully_installed, app.name);
|
||||
|
@ -101,10 +101,6 @@ public class AppDetailsAdapterTest extends FDroidProviderTest {
|
||||
}
|
||||
|
||||
private final AppDetailsRecyclerViewAdapter.AppDetailsRecyclerViewAdapterCallbacks dummyCallbacks = new AppDetailsRecyclerViewAdapter.AppDetailsRecyclerViewAdapterCallbacks() { // NOCHECKSTYLE LineLength
|
||||
@Override
|
||||
public boolean isAppDownloading() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enableAndroidBeam() {
|
||||
@ -131,11 +127,6 @@ public class AppDetailsAdapterTest extends FDroidProviderTest {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void upgradeApk() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallApk() {
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user