diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2014-07-21 18:27:22 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-07-19 22:40:06 +0000 |
commit | c5a8704dc4f6b303441a6773f73148799c29e485 (patch) | |
tree | 6428afe246e195571b145bfc6500f3fc1da8fcfa | |
parent | 871f3f8bbe66f681cf9cedffa8c9366e2d8d2d82 (diff) | |
parent | 651077bdd603bb182be039925fd17bdf0da15016 (diff) | |
download | android_packages_apps_Trebuchet-c5a8704dc4f6b303441a6773f73148799c29e485.tar.gz android_packages_apps_Trebuchet-c5a8704dc4f6b303441a6773f73148799c29e485.tar.bz2 android_packages_apps_Trebuchet-c5a8704dc4f6b303441a6773f73148799c29e485.zip |
Merge "Adding app widget restore support in Launcher3 for android L" into ub-now-master
-rw-r--r-- | AndroidManifest.xml | 7 | ||||
-rw-r--r-- | res/drawable/bg_appwidget_not_ready.xml | 24 | ||||
-rw-r--r-- | res/layout/appwidget_not_ready.xml | 28 | ||||
-rw-r--r-- | res/values/colors.xml | 1 | ||||
-rw-r--r-- | res/values/strings.xml | 3 | ||||
-rw-r--r-- | src/com/android/launcher3/AppWidgetsRestoredReceiver.java | 90 | ||||
-rw-r--r-- | src/com/android/launcher3/DeleteDropTarget.java | 2 | ||||
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 24 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherAppWidgetHostView.java | 26 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherAppWidgetInfo.java | 22 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherModel.java | 76 | ||||
-rw-r--r-- | src/com/android/launcher3/Workspace.java | 64 |
12 files changed, 327 insertions, 40 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 67d86d2fa..49efc5d89 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -188,6 +188,13 @@ </intent-filter> </receiver> + <!-- Intent received used to initialize a restored widget --> + <receiver android:name="com.android.launcher3.AppWidgetsRestoredReceiver" > + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_HOST_RESTORED"/> + </intent-filter> + </receiver> + <!-- New user initialization; set up initial wallpaper --> <receiver android:name="com.android.launcher3.UserInitializeReceiver" diff --git a/res/drawable/bg_appwidget_not_ready.xml b/res/drawable/bg_appwidget_not_ready.xml new file mode 100644 index 000000000..a8b56c2f8 --- /dev/null +++ b/res/drawable/bg_appwidget_not_ready.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle" > + + <corners android:radius="5dp" /> + + <solid android:color="@android:color/white" /> + +</shape>
\ No newline at end of file diff --git a/res/layout/appwidget_not_ready.xml b/res/layout/appwidget_not_ready.xml new file mode 100644 index 000000000..f5f2aab21 --- /dev/null +++ b/res/layout/appwidget_not_ready.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2009 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. +--> + +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="20dip" + android:paddingRight="20dip" + android:layout_margin="10dip" + android:gravity="center" + android:background="@drawable/bg_appwidget_not_ready" + android:textAppearance="?android:attr/textAppearanceMediumInverse" + android:textColor="@color/appwidget_not_ready_color" + android:text="@string/gadget_pending_text" + /> diff --git a/res/values/colors.xml b/res/values/colors.xml index d1d33a0f6..27a5b61ee 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -26,6 +26,7 @@ <color name="bubble_dark_background">#20000000</color> <color name="appwidget_error_color">#FCCC</color> + <color name="appwidget_not_ready_color">#F48F</color> <color name="workspace_all_apps_and_delete_zone_text_color">#CCFFFFFF</color> <color name="workspace_all_apps_and_delete_zone_text_shadow_color">#A0000000</color> diff --git a/res/values/strings.xml b/res/values/strings.xml index 7c5ee9c86..1ee1be6fd 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -190,6 +190,9 @@ s --> <!-- Text to show user in place of a gadget when we can't display it properly --> <string name="gadget_error_text">Problem loading widget</string> + <!-- Text to show user in place of a gadget when it is not yet ready/initialized. --> + <string name="gadget_pending_text" translatable="false">Widget not ready</string> + <!-- Text to inform the user that they can't uninstall a system application --> <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string> diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java new file mode 100644 index 000000000..9fef7f934 --- /dev/null +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -0,0 +1,90 @@ +package com.android.launcher3; + +import android.appwidget.AppWidgetHost; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.AsyncTask; +import android.util.Log; + +import com.android.launcher3.LauncherSettings.Favorites; + +import java.util.ArrayList; +import java.util.List; + +public class AppWidgetsRestoredReceiver extends BroadcastReceiver { + + private static final String TAG = "AppWidgetsRestoredReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + if (AppWidgetManager.ACTION_APPWIDGET_HOST_RESTORED.equals(intent.getAction())) { + int[] oldIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS); + int[] newIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS); + if (oldIds.length == newIds.length) { + restoreAppWidgetIds(context, oldIds, newIds); + } else { + Log.e(TAG, "Invalid host restored received"); + } + } + } + + /** + * Updates the app widgets whose id has changed during the restore process. + */ + static void restoreAppWidgetIds(Context context, int[] oldWidgetIds, int[] newWidgetIds) { + final ContentResolver cr = context.getContentResolver(); + final List<Integer> idsToRemove = new ArrayList<>(); + final AppWidgetManager widgets = AppWidgetManager.getInstance(context); + + for (int i = 0; i < oldWidgetIds.length; i++) { + Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]); + + final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]); + + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i]); + values.put(LauncherSettings.Favorites.RESTORED, LauncherModel.isValidProvider(provider) + ? LauncherAppWidgetInfo.RESTORE_COMPLETED + : LauncherAppWidgetInfo.RESTORE_PROVIDER_PENDING); + + String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) }; + + int result = cr.update(Favorites.CONTENT_URI, values, + "appWidgetId=? and restored=1", widgetIdParams); + if (result == 0) { + Cursor cursor = cr.query(Favorites.CONTENT_URI, + new String[] {Favorites.APPWIDGET_ID}, + "appWidgetId=?", widgetIdParams, null); + try { + if (!cursor.moveToFirst()) { + // The widget no long exists. + idsToRemove.add(newWidgetIds[i]); + } + } finally { + cursor.close(); + } + } + } + // Unregister the widget IDs which are not present on the workspace. This could happen + // when a widget place holder is removed from workspace, before this method is called. + if (!idsToRemove.isEmpty()) { + final AppWidgetHost appWidgetHost = + new AppWidgetHost(context, Launcher.APPWIDGET_HOST_ID); + new AsyncTask<Void, Void, Void>() { + public Void doInBackground(Void ... args) { + for (Integer id : idsToRemove) { + appWidgetHost.deleteAppWidgetId(id); + Log.e(TAG, "Widget no longer present, appWidgetId=" + id); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); + } + } +} diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java index 3d45432c2..4c3388e05 100644 --- a/src/com/android/launcher3/DeleteDropTarget.java +++ b/src/com/android/launcher3/DeleteDropTarget.java @@ -326,7 +326,7 @@ public class DeleteDropTarget extends ButtonDropTarget { final LauncherAppWidgetInfo launcherAppWidgetInfo = (LauncherAppWidgetInfo) item; final LauncherAppWidgetHost appWidgetHost = mLauncher.getAppWidgetHost(); - if (appWidgetHost != null) { + if ((appWidgetHost != null) && launcherAppWidgetInfo.isWidgetIdValid()) { // Deleting an app widget ID is a void call but writes to disk before returning // to the caller... new AsyncTask<Void, Void, Void>() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index d3a436237..5e05f927d 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -89,6 +89,7 @@ import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.animation.AccelerateDecelerateInterpolator; @@ -102,6 +103,8 @@ import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +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.UserHandleCompat; @@ -4387,13 +4390,20 @@ public class Launcher extends Activity } final Workspace workspace = mWorkspace; - final int appWidgetId = item.appWidgetId; - final AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); - if (DEBUG_WIDGETS) { - Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); - } + final AppWidgetProviderInfo appWidgetInfo; + if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { + final int appWidgetId = item.appWidgetId; + appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); + if (DEBUG_WIDGETS) { + Log.d(TAG, "bindAppWidget: id=" + item.appWidgetId + " belongs to component " + appWidgetInfo.provider); + } - item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); + } else { + appWidgetInfo = null; + item.hostView = new LauncherAppWidgetHostView(this, false); + item.hostView.updateAppWidget(null); + } item.hostView.setTag(item); item.onBindAppWidget(this); @@ -4553,7 +4563,7 @@ public class Launcher extends Activity } if (mWorkspace != null) { - mWorkspace.updateShortcuts(apps); + mWorkspace.updateShortcutsAndWidgets(apps); } if (!LauncherAppState.isDisableAllApps() && diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index f47fd13ec..7eb005255 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -18,6 +18,7 @@ package com.android.launcher3; import android.appwidget.AppWidgetHostView; import android.content.Context; +import android.os.Bundle; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -39,12 +40,28 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc private float mSlop; + private boolean mWidgetReady; + public LauncherAppWidgetHostView(Context context) { + this(context, true); + } + + public LauncherAppWidgetHostView(Context context, boolean widgetReady) { super(context); mContext = context; mLongPressHelper = new CheckLongPressHelper(this); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDragLayer = ((Launcher) context).getDragLayer(); + mWidgetReady = widgetReady; + } + + @Override + public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, + int maxHeight) { + // If the widget is not yet ready, the app widget size cannot be updated. + if (mWidgetReady) { + super.updateAppWidgetSize(newOptions, minWidth, minHeight, maxWidth, maxHeight); + } } @Override @@ -53,6 +70,15 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc } @Override + protected View getDefaultView() { + if (mWidgetReady) { + return super.getDefaultView(); + } else { + return mInflater.inflate(R.layout.appwidget_not_ready, this, false); + } + } + + @Override public void updateAppWidget(RemoteViews remoteViews) { // Store the orientation in which the widget was inflated mPreviousOrientation = mContext.getResources().getConfiguration().orientation; diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index bec1f9eb3..b3ac12b37 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -28,6 +28,18 @@ import com.android.launcher3.compat.UserHandleCompat; */ public class LauncherAppWidgetInfo extends ItemInfo { + public static final int RESTORE_COMPLETED = 0; + + /** + * This is set during the package backup creation. + */ + public static final int RESTORE_REMAP_PENDING = 1; + + /** + * Widget provider is not yet installed. + */ + public static final int RESTORE_PROVIDER_PENDING = 2; + /** * Indicates that the widget hasn't been instantiated yet. */ @@ -45,6 +57,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { int minWidth = -1; int minHeight = -1; + /** + * Indicates the restore status of the widget. + */ + int restoreStatus; + private boolean mHasNotifiedInitialWidgetSizeChanged; /** @@ -64,6 +81,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { spanY = -1; // We only support app widgets on current user. user = UserHandleCompat.myUserHandle(); + restoreStatus = RESTORE_COMPLETED; } @Override @@ -101,4 +119,8 @@ public class LauncherAppWidgetInfo extends ItemInfo { super.unbind(); hostView = null; } + + public final boolean isWidgetIdValid() { + return restoreStatus != RESTORE_REMAP_PENDING; + } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index a8bace8e2..db5e7cf68 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -2125,31 +2125,54 @@ public class LauncherModel extends BroadcastReceiver // Read all Launcher-specific widget details int appWidgetId = c.getInt(appWidgetIdIndex); String savedProvider = c.getString(appWidgetProviderIndex); - id = c.getLong(idIndex); - final AppWidgetProviderInfo provider = - widgets.getAppWidgetInfo(appWidgetId); - - if (!isSafeMode && (provider == null || provider.provider == null || - provider.provider.getPackageName() == null)) { - String log = "Deleting widget that isn't installed anymore: id=" - + id + " appWidgetId=" + appWidgetId; + final int restoreStatus = c.getInt(restoredIndex); + final boolean restorePending = Utilities.isLmp() + && (restoreStatus == + LauncherAppWidgetInfo.RESTORE_REMAP_PENDING); + final boolean providerPending = Utilities.isLmp() + && (restoreStatus == + LauncherAppWidgetInfo.RESTORE_PROVIDER_PENDING); + + // Do not try to get the provider if restore is pending, as the + // widget id is invalid, and it might point to some other provider. + final AppWidgetProviderInfo provider = restorePending ? null + : widgets.getAppWidgetInfo(appWidgetId); + boolean providerValid = isValidProvider(provider); + + // Skip provider check, + // 1. when the widget id re-map is pending + // 2. provider is pending install for a restored widget + if (!isSafeMode && !providerPending && !restorePending + && !providerValid) { + String log = "Deleting widget that isn't installed anymore: " + + "id=" + id + " appWidgetId=" + appWidgetId; Log.e(TAG, log); Launcher.addDumpLog(TAG, log, false); itemsToRemove.add(id); } else { - appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - provider.provider); + if (providerValid) { + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + provider.provider); + int[] minSpan = + Launcher.getMinSpanForWidget(context, provider); + appWidgetInfo.minSpanX = minSpan[0]; + appWidgetInfo.minSpanY = minSpan[1]; + } else { + Log.v(TAG, "Widget restore pending id=" + id + + " appWidgetId=" + appWidgetId + + " status =" + restoreStatus); + appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, + ComponentName.unflattenFromString(savedProvider)); + appWidgetInfo.restoreStatus = restoreStatus; + } appWidgetInfo.id = id; appWidgetInfo.screenId = c.getInt(screenIndex); appWidgetInfo.cellX = c.getInt(cellXIndex); appWidgetInfo.cellY = c.getInt(cellYIndex); appWidgetInfo.spanX = c.getInt(spanXIndex); appWidgetInfo.spanY = c.getInt(spanYIndex); - int[] minSpan = Launcher.getMinSpanForWidget(context, provider); - appWidgetInfo.minSpanX = minSpan[0]; - appWidgetInfo.minSpanY = minSpan[1]; container = c.getInt(containerIndex); if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && @@ -2169,14 +2192,20 @@ public class LauncherModel extends BroadcastReceiver } break; } - String providerName = provider.provider.flattenToString(); - if (!providerName.equals(savedProvider)) { - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, - providerName); - String where = BaseColumns._ID + "= ?"; - String[] args = {Integer.toString(c.getInt(idIndex))}; - contentResolver.update(contentUri, values, where, args); + + if (providerValid) { + String providerName = provider.provider.flattenToString(); + + if (!providerName.equals(savedProvider) || providerPending) { + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, + providerName); + values.put(LauncherSettings.Favorites.RESTORED, + LauncherAppWidgetInfo.RESTORE_COMPLETED); + String where = BaseColumns._ID + "= ?"; + String[] args = {Long.toString(id)}; + contentResolver.update(contentUri, values, where, args); + } } sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo); sBgAppWidgets.add(appWidgetInfo); @@ -3605,6 +3634,11 @@ public class LauncherModel extends BroadcastReceiver } }; + static boolean isValidProvider(AppWidgetProviderInfo provider) { + return (provider != null) && (provider.provider != null) + && (provider.provider.getPackageName() != null); + } + public void dumpState() { Log.d(TAG, "mCallbacks=" + mCallbacks); AppInfo.dumpApplicationInfoList(TAG, "mAllAppsList.data", mBgAllAppsList.data); diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 267dde800..1e264588c 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -30,6 +30,8 @@ import android.app.WallpaperManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; @@ -47,6 +49,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.IBinder; import android.os.Parcelable; +import android.provider.BaseColumns; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.util.Log; @@ -61,15 +64,16 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import android.widget.TextView; -import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.FolderIcon.FolderRingAnimator; import com.android.launcher3.Launcher.CustomContentCallbacks; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.compat.UserHandleCompat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Set; /** * The workspace is a wide area with a wallpaper and a finite number of pages. @@ -4574,7 +4578,7 @@ public class Workspace extends SmoothPagedView public Folder getFolderForTag(final Object tag) { final Folder[] value = new Folder[1]; - mapOverShortcuts(MAP_NO_RECURSE, new ShortcutOperator() { + mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { if (v instanceof Folder) { @@ -4592,7 +4596,7 @@ public class Workspace extends SmoothPagedView public View getViewForTag(final Object tag) { final View[] value = new View[1]; - mapOverShortcuts(MAP_NO_RECURSE, new ShortcutOperator() { + mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { if (v.getTag() == tag) { @@ -4606,7 +4610,7 @@ public class Workspace extends SmoothPagedView } void clearDropTargets() { - mapOverShortcuts(MAP_NO_RECURSE, new ShortcutOperator() { + mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { if (v instanceof DropTarget) { @@ -4760,9 +4764,9 @@ public class Workspace extends SmoothPagedView } } - interface ShortcutOperator { + interface ItemOperator { /** - * Process the next shortcut, possibly with side-effect on {@link ShortcutOperator#value}. + * Process the next itemInfo, possibly with side-effect on {@link ItemOperator#value}. * * @param info info for the shortcut * @param view view for the shortcut @@ -4773,12 +4777,12 @@ public class Workspace extends SmoothPagedView } /** - * Map the operator over the shortcuts, return the first-non-null value. + * Map the operator over the shortcuts and widgets, return the first-non-null value. * * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - void mapOverShortcuts(boolean recurse, ShortcutOperator op) { + void mapOverItems(boolean recurse, ItemOperator op) { ArrayList<ShortcutAndWidgetContainer> containers = getAllShortcutAndWidgetContainers(); final int containerCount = containers.size(); for (int containerIdx = 0; containerIdx < containerCount; containerIdx++) { @@ -4809,14 +4813,16 @@ public class Workspace extends SmoothPagedView } } - void updateShortcuts(ArrayList<AppInfo> apps) { + void updateShortcutsAndWidgets(ArrayList<AppInfo> apps) { // Create a map of the apps to test against final HashMap<ComponentName, AppInfo> appsMap = new HashMap<ComponentName, AppInfo>(); + final HashSet<String> pkgNames = new HashSet<>(); for (AppInfo ai : apps) { appsMap.put(ai.componentName, ai); + pkgNames.add(ai.componentName.getPackageName()); } - mapOverShortcuts(MAP_RECURSE, new ShortcutOperator() { + mapOverItems(MAP_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { if (info instanceof ShortcutInfo) { @@ -4829,6 +4835,8 @@ public class Workspace extends SmoothPagedView return false; } }); + + restorePendingWidgets(pkgNames); } public void removeAbandonedPromise(BubbleTextView abandonedIcon, UserHandleCompat user) { @@ -4844,7 +4852,7 @@ public class Workspace extends SmoothPagedView } public void updatePackageState(final String pkgName, final int state) { - mapOverShortcuts(MAP_RECURSE, new ShortcutOperator() { + mapOverItems(MAP_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { if (info instanceof ShortcutInfo @@ -4857,6 +4865,40 @@ public class Workspace extends SmoothPagedView return false; } }); + + if (state == ShortcutInfo.PACKAGE_STATE_DEFAULT) { + // Update any pending widget + HashSet<String> packages = new HashSet<>(); + packages.add(pkgName); + restorePendingWidgets(packages); + } + } + + private void restorePendingWidgets(final Set<String> installedPackaged) { + final ContentResolver contentResolver = getContext().getContentResolver(); + // Iterate non recursively as widgets can't be inside a folder. + mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + + @Override + public boolean evaluate(ItemInfo info, View v, View parent) { + if (info instanceof LauncherAppWidgetInfo) { + LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; + if (widgetInfo.restoreStatus == LauncherAppWidgetInfo.RESTORE_PROVIDER_PENDING + && installedPackaged.contains(widgetInfo.providerName.getPackageName())) { + // Package installed. Update pending widgets. + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.RESTORED, + LauncherAppWidgetInfo.RESTORE_COMPLETED); + String where = BaseColumns._ID + "= ?"; + String[] args = {Long.toString(widgetInfo.id)}; + contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, + values, where, args); + } + } + // process all the widget + return false; + } + }); } private void moveToScreen(int page, boolean animate) { |