From e755d469d40b95e763a9dcb67d0e4f511d1948dd Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Tue, 22 Jul 2014 13:48:29 -0700 Subject: Implementing a package install progress listener for L issue: 15835307 Change-Id: I71aaea087963f2e0e1206447190cbe23c174057d --- .../launcher3/compat/LauncherAppsCompat.java | 7 - .../launcher3/compat/PackageInstallerCompat.java | 67 ++++++++ .../compat/PackageInstallerCompatV16.java | 170 +++++++++++++++++++++ .../launcher3/compat/PackageInstallerCompatVL.java | 159 +++++++++++++++++++ 4 files changed, 396 insertions(+), 7 deletions(-) create mode 100644 src/com/android/launcher3/compat/PackageInstallerCompat.java create mode 100644 src/com/android/launcher3/compat/PackageInstallerCompatV16.java create mode 100644 src/com/android/launcher3/compat/PackageInstallerCompatVL.java (limited to 'src/com/android/launcher3/compat') diff --git a/src/com/android/launcher3/compat/LauncherAppsCompat.java b/src/com/android/launcher3/compat/LauncherAppsCompat.java index 8d978d4a3..cc0203d27 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompat.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompat.java @@ -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 { diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java new file mode 100644 index 000000000..89a2157eb --- /dev/null +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -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; + } + } +} diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatV16.java b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java new file mode 100644 index 000000000..653a88c7d --- /dev/null +++ b/src/com/android/launcher3/compat/PackageInstallerCompatV16.java @@ -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 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 update = new ArrayList<>(); + update.add(installInfo); + sendUpdate(app, update); + } + } + } + + private void sendUpdate(LauncherAppState app, ArrayList 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; + } +} diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java new file mode 100644 index 000000000..7f6302f14 --- /dev/null +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -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 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 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) { } + + }; +} -- cgit v1.2.3