Implementing a package install progress listener for L

issue: 15835307

Change-Id: I71aaea087963f2e0e1206447190cbe23c174057d
This commit is contained in:
Sunny Goyal
2014-07-22 13:48:29 -07:00
parent c5b6ac7215
commit e755d469d4
10 changed files with 498 additions and 103 deletions
+34 -51
View File
@@ -434,65 +434,48 @@ public class BubbleTextView extends TextView {
}
public void applyState() {
final int progressLevel;
final int state = getState();
if (DEBUG) Log.d(TAG, "applying icon state: " + state);
if (getTag() instanceof ShortcutInfo) {
ShortcutInfo info = (ShortcutInfo) getTag();
final int state = info.getState();
switch(state) {
case ShortcutInfo.PACKAGE_STATE_DEFAULT:
super.setText(mDefaultText);
progressLevel = 100;
break;
final int progressLevel;
if (DEBUG) Log.d(TAG, "applying icon state: " + state);
case ShortcutInfo.PACKAGE_STATE_ENQUEUED:
setText(R.string.package_state_enqueued);
progressLevel = 0;
break;
switch(state) {
case ShortcutInfo.PACKAGE_STATE_DEFAULT:
progressLevel = 100;
break;
case ShortcutInfo.PACKAGE_STATE_DOWNLOADING:
setText(R.string.package_state_downloading);
// TODO(sunnygoyal): fix progress
progressLevel = 30;
break;
case ShortcutInfo.PACKAGE_STATE_INSTALLING:
setText(R.string.package_state_installing);
progressLevel = info.getProgress();
break;
case ShortcutInfo.PACKAGE_STATE_INSTALLING:
setText(R.string.package_state_installing);
progressLevel = 100;
break;
case ShortcutInfo.PACKAGE_STATE_ERROR:
case ShortcutInfo.PACKAGE_STATE_UNKNOWN:
default:
progressLevel = 0;
setText(R.string.package_state_unknown);
break;
}
case ShortcutInfo.PACKAGE_STATE_ERROR:
setText(R.string.package_state_error);
progressLevel = 0;
break;
Drawable[] drawables = getCompoundDrawables();
Drawable top = drawables[1];
if (top != null) {
final PreloadIconDrawable preloadDrawable;
if (top instanceof PreloadIconDrawable) {
preloadDrawable = (PreloadIconDrawable) top;
} else {
preloadDrawable = new PreloadIconDrawable(top, getResources());
setCompoundDrawables(drawables[0], preloadDrawable, drawables[2], drawables[3]);
}
case ShortcutInfo.PACKAGE_STATE_UNKNOWN:
default:
progressLevel = 0;
setText(R.string.package_state_unknown);
break;
}
preloadDrawable.setLevel(progressLevel);
if (state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
preloadDrawable.maybePerformFinishedAnimation();
}
Drawable[] drawables = getCompoundDrawables();
Drawable top = drawables[1];
if ((top != null) && !(top instanceof PreloadIconDrawable)) {
top = new PreloadIconDrawable(top, getResources());
setCompoundDrawables(drawables[0], top, drawables[2], drawables[3]);
}
if (top != null) {
top.setLevel(progressLevel);
if ((top instanceof PreloadIconDrawable)
&& (state == ShortcutInfo.PACKAGE_STATE_DEFAULT)) {
((PreloadIconDrawable) top).maybePerformFinishedAnimation();
}
}
}
private int getState() {
if (! (getTag() instanceof ShortcutInfo)) {
return ShortcutInfo.PACKAGE_STATE_DEFAULT;
} else {
ShortcutInfo info = (ShortcutInfo) getTag();
return info.getState();
}
}
}
+11 -8
View File
@@ -82,10 +82,9 @@ import android.view.Menu;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.Window;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -93,9 +92,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.InputMethodManager;
import android.widget.Advanceable;
import android.widget.FrameLayout;
@@ -107,10 +104,10 @@ import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.PagedView.PageSwitchListener;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.PagedView.PageSwitchListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -1055,12 +1052,15 @@ public class Launcher extends Activity
}
mWorkspace.updateInteractionForState();
mWorkspace.onResume();
PackageInstallerCompat.getInstance(this).onResume();
}
@Override
protected void onPause() {
// Ensure that items added to Launcher are queued until Launcher returns
InstallShortcutReceiver.enableInstallQueue();
PackageInstallerCompat.getInstance(this).onPause();
super.onPause();
mPaused = true;
@@ -2028,6 +2028,7 @@ public class Launcher extends Activity
mWorkspace = null;
mDragController = null;
PackageInstallerCompat.getInstance(this).onStop();
LauncherAnimUtils.onDestroyActivity();
}
@@ -4478,6 +4479,7 @@ public class Launcher extends Activity
mWorkspace.getUniqueComponents(true, null);
mIntentsOnWorkspaceFromUpgradePath = mWorkspace.getUniqueComponents(true, null);
}
PackageInstallerCompat.getInstance(this).onFinishBind();
}
private void sendLoadingCompleteBroadcastIfNecessary() {
@@ -4591,9 +4593,10 @@ public class Launcher extends Activity
*
* Implementation of the method from LauncherModel.Callbacks.
*/
public void updatePackageState(String pkgName, int state) {
@Override
public void updatePackageState(ArrayList<PackageInstallInfo> installInfo) {
if (mWorkspace != null) {
mWorkspace.updatePackageState(pkgName, state);
mWorkspace.updatePackageState(installInfo);
}
}
@@ -17,7 +17,11 @@
package com.android.launcher3;
import android.app.SearchManager;
import android.content.*;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -25,8 +29,10 @@ import android.os.Handler;
import android.util.Log;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
private static final String TAG = "LauncherAppState";
@@ -251,8 +257,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks {
return getInstance().mBuildInfo.isDogfoodBuild();
}
public void setPackageState(String pkgName, int state) {
if (DEBUG) Log.d(TAG, "setPackageState(" + pkgName + ", " + state + ")");
mModel.setPackageState(pkgName, state);
public void setPackageState(ArrayList<PackageInstallInfo> installInfo) {
mModel.setPackageState(installInfo);
}
}
+4 -3
View File
@@ -55,6 +55,7 @@ import android.util.Pair;
import com.android.launcher3.InstallWidgetReceiver.WidgetMimeTypeHandlerData;
import com.android.launcher3.compat.LauncherActivityInfoCompat;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
@@ -195,7 +196,7 @@ public class LauncherModel extends BroadcastReceiver
ArrayList<ItemInfo> addAnimated,
ArrayList<AppInfo> addedApps);
public void bindAppsUpdated(ArrayList<AppInfo> apps);
public void updatePackageState(String pkgName, int state);
public void updatePackageState(ArrayList<PackageInstallInfo> installInfo);
public void bindComponentsRemoved(ArrayList<String> packageNames,
ArrayList<AppInfo> appInfos, UserHandleCompat user);
public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
@@ -332,13 +333,13 @@ public class LauncherModel extends BroadcastReceiver
return null;
}
public void setPackageState(final String pkgName, final int state) {
public void setPackageState(final ArrayList<PackageInstallInfo> installInfo) {
// Process the updated package state
Runnable r = new Runnable() {
public void run() {
Callbacks callbacks = mCallbacks != null ? mCallbacks.get() : null;
if (callbacks != null) {
callbacks.updatePackageState(pkgName, state);
callbacks.updatePackageState(installInfo);
}
}
};
+18 -11
View File
@@ -42,13 +42,7 @@ public class ShortcutInfo extends ItemInfo {
public static final int PACKAGE_STATE_DEFAULT = 0;
/** {@link #mState} meaning some external entity has promised to install this package. */
public static final int PACKAGE_STATE_ENQUEUED = 1;
/** {@link #mState} meaning but some external entity is downloading this package. */
public static final int PACKAGE_STATE_DOWNLOADING = 2;
/** {@link #mState} meaning some external entity is installing this package. */
public static final int PACKAGE_STATE_INSTALLING = 3;
public static final int PACKAGE_STATE_INSTALLING = 1;
/**
* The intent used to start the application.
@@ -89,6 +83,11 @@ public class ShortcutInfo extends ItemInfo {
*/
protected int mState;
/**
* The installation progress [0-100] of the package that this shortcut represents.
*/
protected int mProgress;
long firstInstallTime;
int flags = 0;
@@ -237,16 +236,24 @@ public class ShortcutInfo extends ItemInfo {
public boolean isAbandoned() {
return isPromise()
&& (mState == ShortcutInfo.PACKAGE_STATE_ERROR
|| mState == ShortcutInfo.PACKAGE_STATE_UNKNOWN);
&& (mState == PACKAGE_STATE_ERROR
|| mState == PACKAGE_STATE_UNKNOWN);
}
public int getState() {
return mState;
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
mProgress = progress;
}
public void setState(int state) {
mState = state;
}
public int getState() {
return mState;
}
}
+26 -19
View File
@@ -67,6 +67,7 @@ import android.widget.TextView;
import com.android.launcher3.FolderIcon.FolderRingAnimator;
import com.android.launcher3.Launcher.CustomContentCallbacks;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserHandleCompat;
import java.util.ArrayList;
@@ -4864,26 +4865,32 @@ public class Workspace extends SmoothPagedView
}
}
public void updatePackageState(final String pkgName, final int state) {
mapOverItems(MAP_RECURSE, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v, View parent) {
if (info instanceof ShortcutInfo
&& ((ShortcutInfo) info).isPromiseFor(pkgName)
&& v instanceof BubbleTextView) {
((ShortcutInfo) info).setState(state);
((BubbleTextView)v).applyState();
}
// process all the shortcuts
return false;
}
});
public void updatePackageState(ArrayList<PackageInstallInfo> installInfos) {
HashSet<String> completedPackages = new HashSet<>();
if (state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
// Update any pending widget
HashSet<String> packages = new HashSet<String>();
packages.add(pkgName);
restorePendingWidgets(packages);
for (final PackageInstallInfo installInfo : installInfos) {
mapOverItems(MAP_RECURSE, new ItemOperator() {
@Override
public boolean evaluate(ItemInfo info, View v, View parent) {
if (info instanceof ShortcutInfo
&& ((ShortcutInfo) info).isPromiseFor(installInfo.packageName)
&& v instanceof BubbleTextView) {
((ShortcutInfo) info).setProgress(installInfo.progress);
((ShortcutInfo) info).setState(installInfo.state);
((BubbleTextView)v).applyState();
}
// process all the shortcuts
return false;
}
});
if (installInfo.state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
completedPackages.add(installInfo.packageName);
}
}
if (!completedPackages.isEmpty()) {
restorePendingWidgets(completedPackages);
}
}
@@ -20,17 +20,10 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class LauncherAppsCompat {
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.compat;
import android.content.Context;
import com.android.launcher3.Utilities;
public abstract class PackageInstallerCompat {
private static final Object sInstanceLock = new Object();
private static PackageInstallerCompat sInstance;
public static PackageInstallerCompat getInstance(Context context) {
synchronized (sInstanceLock) {
if (sInstance == null) {
if (Utilities.isLmp()) {
sInstance = new PackageInstallerCompatVL(context);
} else {
sInstance = new PackageInstallerCompatV16(context) { };
}
}
return sInstance;
}
}
public abstract void onPause();
public abstract void onResume();
public abstract void onFinishBind();
public abstract void onStop();
public abstract void recordPackageUpdate(String packageName, int state, int progress);
public static final class PackageInstallInfo {
public final String packageName;
public int state;
public int progress;
public PackageInstallInfo(String packageName) {
this.packageName = packageName;
}
public PackageInstallInfo(String packageName, int state, int progress) {
this.packageName = packageName;
this.state = state;
this.progress = progress;
}
}
}
@@ -0,0 +1,170 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.compat;
import android.content.Context;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.ShortcutInfo;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.json.JSONTokener;
import java.util.ArrayList;
public class PackageInstallerCompatV16 extends PackageInstallerCompat {
private static final String TAG = "PackageInstallerCompatV16";
private static final boolean DEBUG = false;
private static final String KEY_PROGRESS = "progress";
private static final String KEY_STATE = "state";
private static final String PREFS =
"com.android.launcher3.compat.PackageInstallerCompatV16.queue";
protected final SharedPreferences mPrefs;
boolean mUseQueue;
boolean mFinishedBind;
boolean mReplayPending;
PackageInstallerCompatV16(Context context) {
mPrefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
}
@Override
public void onPause() {
mUseQueue = true;
if (DEBUG) Log.d(TAG, "updates paused");
}
@Override
public void onResume() {
mUseQueue = false;
if (mFinishedBind) {
replayUpdates();
}
}
@Override
public void onFinishBind() {
mFinishedBind = true;
if (!mUseQueue) {
replayUpdates();
}
}
@Override
public void onStop() { }
private void replayUpdates() {
if (DEBUG) Log.d(TAG, "updates resumed");
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
mReplayPending = true; // try again later
if (DEBUG) Log.d(TAG, "app is null, delaying send");
return;
}
mReplayPending = false;
ArrayList<PackageInstallInfo> updates = new ArrayList<>();
for (String packageName: mPrefs.getAll().keySet()) {
final String json = mPrefs.getString(packageName, null);
if (!TextUtils.isEmpty(json)) {
updates.add(infoFromJson(packageName, json));
}
}
if (!updates.isEmpty()) {
sendUpdate(app, updates);
}
}
/**
* This should be called by the implementations to register a package update.
*/
@Override
public synchronized void recordPackageUpdate(String packageName, int state, int progress) {
SharedPreferences.Editor editor = mPrefs.edit();
PackageInstallInfo installInfo = new PackageInstallInfo(packageName);
installInfo.progress = progress;
installInfo.state = state;
if (state == ShortcutInfo.PACKAGE_STATE_DEFAULT) {
// no longer necessary to track this package
editor.remove(packageName);
if (DEBUG) Log.d(TAG, "no longer tracking " + packageName);
} else {
editor.putString(packageName, infoToJson(installInfo));
if (DEBUG)
Log.d(TAG, "saved state: " + infoToJson(installInfo)
+ " for package: " + packageName);
}
editor.commit();
if (!mUseQueue) {
if (mReplayPending) {
replayUpdates();
} else {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
ArrayList<PackageInstallInfo> update = new ArrayList<>();
update.add(installInfo);
sendUpdate(app, update);
}
}
}
private void sendUpdate(LauncherAppState app, ArrayList<PackageInstallInfo> updates) {
if (app == null) {
mReplayPending = true; // try again later
if (DEBUG) Log.d(TAG, "app is null, delaying send");
} else {
app.setPackageState(updates);
}
}
private static PackageInstallInfo infoFromJson(String packageName, String json) {
PackageInstallInfo info = new PackageInstallInfo(packageName);
try {
JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
info.state = object.getInt(KEY_STATE);
info.progress = object.getInt(KEY_PROGRESS);
} catch (JSONException e) {
Log.e(TAG, "failed to deserialize app state update", e);
}
return info;
}
private static String infoToJson(PackageInstallInfo info) {
String value = null;
try {
JSONStringer json = new JSONStringer()
.object()
.key(KEY_STATE).value(info.state)
.key(KEY_PROGRESS).value(info.progress)
.endObject();
value = json.toString();
} catch (JSONException e) {
Log.e(TAG, "failed to serialize app state update", e);
}
return value;
}
}
@@ -0,0 +1,159 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.compat;
import android.content.Context;
import android.content.pm.InstallSessionInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionCallback;
import android.util.Log;
import android.util.SparseArray;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.ShortcutInfo;
import java.util.ArrayList;
public class PackageInstallerCompatVL extends PackageInstallerCompat {
private static final String TAG = "PackageInstallerCompatVL";
private static final boolean DEBUG = false;
private final SparseArray<InstallSessionInfo> mPendingReplays = new SparseArray<>();
private final PackageInstaller mInstaller;
private boolean mResumed;
private boolean mBound;
PackageInstallerCompatVL(Context context) {
mInstaller = context.getPackageManager().getPackageInstaller();
mResumed = false;
mBound = false;
mInstaller.addSessionCallback(mCallback);
// On start, send updates for all active sessions
for (InstallSessionInfo info : mInstaller.getAllSessions()) {
mPendingReplays.append(info.getSessionId(), info);
}
}
@Override
public void onStop() {
mInstaller.removeSessionCallback(mCallback);
}
@Override
public void onFinishBind() {
mBound = true;
replayUpdates(null);
}
@Override
public void onPause() {
mResumed = false;
}
@Override
public void onResume() {
mResumed = true;
replayUpdates(null);
}
@Override
public void recordPackageUpdate(String packageName, int state, int progress) {
// No op
}
private void replayUpdates(PackageInstallInfo newInfo) {
if (DEBUG) Log.d(TAG, "updates resumed");
if (!mResumed || !mBound) {
// Not yet ready
return;
}
if ((mPendingReplays.size() == 0) && (newInfo == null)) {
// Nothing to update
return;
}
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app == null) {
// Try again later
if (DEBUG) Log.d(TAG, "app is null, delaying send");
return;
}
ArrayList<PackageInstallInfo> updates = new ArrayList<>();
if (newInfo != null) {
updates.add(newInfo);
}
for (int i = mPendingReplays.size() - 1; i > 0; i--) {
InstallSessionInfo session = mPendingReplays.valueAt(i);
if (session.getAppPackageName() != null) {
updates.add(new PackageInstallInfo(session.getAppPackageName(),
ShortcutInfo.PACKAGE_STATE_INSTALLING,
(int) (session.getProgress() * 100)));
}
}
mPendingReplays.clear();
if (!updates.isEmpty()) {
app.setPackageState(updates);
}
}
private final SessionCallback mCallback = new SessionCallback() {
@Override
public void onCreated(int sessionId) {
InstallSessionInfo session = mInstaller.getSessionInfo(sessionId);
if (session != null) {
mPendingReplays.put(sessionId, session);
replayUpdates(null);
}
}
@Override
public void onFinished(int sessionId, boolean success) {
mPendingReplays.remove(sessionId);
InstallSessionInfo session = mInstaller.getSessionInfo(sessionId);
if ((session != null) && (session.getAppPackageName() != null)) {
// Replay all updates with a one time update for this installed package. No
// need to store this record for future updates, as the app list will get
// refreshed on resume.
replayUpdates(new PackageInstallInfo(session.getAppPackageName(),
success ? ShortcutInfo.PACKAGE_STATE_DEFAULT
: ShortcutInfo.PACKAGE_STATE_ERROR, 0));
}
}
@Override
public void onProgressChanged(int sessionId, float progress) {
InstallSessionInfo session = mInstaller.getSessionInfo(sessionId);
if (session != null) {
mPendingReplays.put(sessionId, session);
replayUpdates(null);
}
}
@Override
public void onOpened(int sessionId) { }
@Override
public void onClosed(int sessionId) { }
};
}