From ff572277112ec3d6a6a8c1be274d6fa1019e3648 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 23 Jul 2014 13:58:07 -0700 Subject: [PATCH] Adding support to restore widgets even for jelly beans. > Show 'widget-not-ready' until the widget app is installed > Once the app is installed, bind a new widget id (not required on L if id-remap was received). **Remove the widget if bind failed > If the widget has no configuration screen, show the widget, otherwise show 'setup-widget'. > Clicking 'setup-widget' shows the config screen, and updates the widget on RESULT_OK. issue: 10779035 Change-Id: I2f8b06d09dd6acbc498cdd93edc59c26e5ce17af --- res/values/strings.xml | 5 +- .../launcher3/AppWidgetsRestoredReceiver.java | 12 +- .../android/launcher3/AppsCustomizePagedView.java | 20 ++-- src/com/android/launcher3/ItemInfo.java | 8 +- src/com/android/launcher3/Launcher.java | 130 ++++++++++++++++++++- .../launcher3/LauncherAppWidgetHostView.java | 33 +----- .../android/launcher3/LauncherAppWidgetInfo.java | 18 ++- .../android/launcher3/LauncherBackupHelper.java | 21 ++-- src/com/android/launcher3/LauncherModel.java | 62 ++++++---- .../launcher3/PendingAppWidgetHostView.java | 78 +++++++++++++ src/com/android/launcher3/Workspace.java | 66 ++++++----- 11 files changed, 340 insertions(+), 113 deletions(-) create mode 100644 src/com/android/launcher3/PendingAppWidgetHostView.java diff --git a/res/values/strings.xml b/res/values/strings.xml index 780dcd22a..286e04fe5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -193,9 +193,12 @@ s --> Problem loading widget - + Widget not ready + + Setup widget + This is a system app and can\'t be uninstalled. diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index 80cb52da9..880aaf1ec 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -46,17 +46,21 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { Log.i(TAG, "Widget state restore id " + oldWidgetIds[i] + " => " + newWidgetIds[i]); final AppWidgetProviderInfo provider = widgets.getAppWidgetInfo(newWidgetIds[i]); + final int state; + if (LauncherModel.isValidProvider(provider)) { + state = LauncherAppWidgetInfo.RESTORE_COMPLETED; + } else { + state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; + } 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); + values.put(LauncherSettings.Favorites.RESTORED, state); String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) }; int result = cr.update(Favorites.CONTENT_URI, values, - "appWidgetId=? and restored=1", widgetIdParams); + "appWidgetId=? and (restored & 1) = 1", widgetIdParams); if (result == 0) { Cursor cursor = cr.query(Favorites.CONTENT_URI, new String[] {Favorites.APPWIDGET_ID}, diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java index 0e9969697..5f2a36608 100644 --- a/src/com/android/launcher3/AppsCustomizePagedView.java +++ b/src/com/android/launcher3/AppsCustomizePagedView.java @@ -148,6 +148,8 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen LauncherTransitionable { static final String TAG = "AppsCustomizePagedView"; + private static Rect sTmpRect = new Rect(); + /** * The different content types that this paged view can show. */ @@ -223,8 +225,6 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen private ArrayList mDeferredPrepareLoadWidgetPreviewsTasks = new ArrayList(); - private Rect mTmpRect = new Rect(); - WidgetPreviewLoader mWidgetPreviewLoader; private boolean mInBulkBind; @@ -540,26 +540,26 @@ public class AppsCustomizePagedView extends PagedViewWithDraggableItems implemen mLauncher.getWorkspace().beginDragShared(v, this); } - Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) { + static Bundle getDefaultOptionsForWidget(Launcher launcher, PendingAddWidgetInfo info) { Bundle options = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, mTmpRect); - Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(mLauncher, + AppWidgetResizeFrame.getWidgetSizeRanges(launcher, info.spanX, info.spanY, sTmpRect); + Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(launcher, info.componentName, null); - float density = getResources().getDisplayMetrics().density; + float density = launcher.getResources().getDisplayMetrics().density; int xPaddingDips = (int) ((padding.left + padding.right) / density); int yPaddingDips = (int) ((padding.top + padding.bottom) / density); options = new Bundle(); options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH, - mTmpRect.left - xPaddingDips); + sTmpRect.left - xPaddingDips); options.putInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, - mTmpRect.top - yPaddingDips); + sTmpRect.top - yPaddingDips); options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, - mTmpRect.right - xPaddingDips); + sTmpRect.right - xPaddingDips); options.putInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, - mTmpRect.bottom - yPaddingDips); + sTmpRect.bottom - yPaddingDips); } return options; } diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index c726fb43f..8f96f74eb 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -124,6 +124,12 @@ public class ItemInfo { } ItemInfo(ItemInfo info) { + copyFrom(info); + // tempdebug: + LauncherModel.checkItemInfo(this); + } + + public void copyFrom(ItemInfo info) { id = info.id; cellX = info.cellX; cellY = info.cellY; @@ -134,8 +140,6 @@ public class ItemInfo { container = info.container; user = info.user; contentDescription = info.contentDescription; - // tempdebug: - LauncherModel.checkItemInfo(this); } public Intent getIntent() { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 0ab665d74..111fba8ce 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -65,7 +65,6 @@ import android.os.Handler; import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; -import android.provider.Settings; import android.speech.RecognizerIntent; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -153,6 +152,7 @@ public class Launcher extends Activity private static final int REQUEST_PICK_WALLPAPER = 10; private static final int REQUEST_BIND_APPWIDGET = 11; + private static final int REQUEST_RECONFIGURE_APPWIDGET = 12; /** * IntentStarter uses request codes starting with this. This must be greater than all activity @@ -752,6 +752,9 @@ public class Launcher extends Activity case REQUEST_CREATE_APPWIDGET: completeAddAppWidget(args.appWidgetId, args.container, screenId, null, null); break; + case REQUEST_RECONFIGURE_APPWIDGET: + completeRestoreAppWidget(args.appWidgetId); + break; } // Before adding this resetAddInfo(), after a shortcut was added to a workspace screen, // if you turned the screen off and then back while in All Apps, Launcher would not @@ -854,6 +857,21 @@ public class Launcher extends Activity return; } + if (requestCode == REQUEST_RECONFIGURE_APPWIDGET) { + if (resultCode == RESULT_OK) { + // Update the widget view. + PendingAddArguments args = preparePendingAddArgs(requestCode, data, + pendingAddWidgetId, mPendingAddInfo); + if (workspaceLocked) { + sPendingAddItem = args; + } else { + completeAdd(args); + } + } + // Leave the widget in the pending state if the user canceled the configure. + return; + } + // The pattern used here is that a user PICKs a specific application, // which, depending on the target, might need to CREATE the actual target. @@ -2446,6 +2464,10 @@ public class Launcher extends Activity } } else if (v == mAllAppsButton) { onClickAllAppsButton(v); + } else if (tag instanceof LauncherAppWidgetInfo) { + if (v instanceof PendingAppWidgetHostView) { + onClickPendingWidget((PendingAppWidgetHostView) v); + } } } @@ -2454,6 +2476,27 @@ public class Launcher extends Activity } /** + * Event handler for the app widget view which has not fully restored. + */ + public void onClickPendingWidget(PendingAppWidgetHostView v) { + if (v.isReadyForClickSetup()) { + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); + int widgetId = info.appWidgetId; + AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(widgetId); + if (appWidgetInfo != null) { + mPendingAddWidgetInfo = appWidgetInfo; + mPendingAddInfo.copyFrom(info); + mPendingAddWidgetId = widgetId; + + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE); + intent.setComponent(appWidgetInfo.configure); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, info.appWidgetId); + Utilities.startActivityForResultSafely(this, intent, REQUEST_RECONFIGURE_APPWIDGET); + } + } + } + + /** * Event handler for the search button * * @param v The view that was clicked. @@ -4398,7 +4441,64 @@ public class Launcher extends Activity } final Workspace workspace = mWorkspace; - final AppWidgetProviderInfo appWidgetInfo; + AppWidgetProviderInfo appWidgetInfo; + if (((item.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0) && + ((item.restoreStatus & LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) != 0)) { + + appWidgetInfo = mModel.findAppWidgetProviderInfoWithComponent(this, item.providerName); + if (appWidgetInfo == null) { + if (DEBUG_WIDGETS) { + Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + + " belongs to component " + item.providerName + + ", as the povider is null"); + } + LauncherModel.deleteItemFromDatabase(this, item); + return; + } + // Note: This assumes that the id remap broadcast is received before this step. + // If that is not the case, the id remap will be ignored and user may see the + // click to setup view. + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(appWidgetInfo, null, null); + pendingInfo.spanX = item.spanX; + pendingInfo.spanY = item.spanY; + pendingInfo.minSpanX = item.minSpanX; + pendingInfo.minSpanY = item.minSpanY; + Bundle options = + AppsCustomizePagedView.getDefaultOptionsForWidget(this, pendingInfo); + + boolean success = false; + int newWidgetId = mAppWidgetHost.allocateAppWidgetId(); + if (options != null) { + success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, + appWidgetInfo.provider, options); + } else { + success = mAppWidgetManager.bindAppWidgetIdIfAllowed(newWidgetId, + appWidgetInfo.provider); + } + + // TODO consider showing a permission dialog when the widget is clicked. + if (!success) { + mAppWidgetHost.deleteAppWidgetId(newWidgetId); + if (DEBUG_WIDGETS) { + Log.d(TAG, "Removing restored widget: id=" + item.appWidgetId + + " belongs to component " + item.providerName + + ", as the launcher is unable to bing a new widget id"); + } + LauncherModel.deleteItemFromDatabase(this, item); + return; + } + + item.appWidgetId = newWidgetId; + + // If the widget has a configure activity, it is still needs to set it up, otherwise + // the widget is ready to go. + item.restoreStatus = (appWidgetInfo.configure == null) + ? LauncherAppWidgetInfo.RESTORE_COMPLETED + : LauncherAppWidgetInfo.FLAG_UI_NOT_READY; + + LauncherModel.updateItemInDatabase(this, item); + } + if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { final int appWidgetId = item.appWidgetId; appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId); @@ -4409,8 +4509,9 @@ public class Launcher extends Activity item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); } else { appWidgetInfo = null; - item.hostView = new LauncherAppWidgetHostView(this, false); + item.hostView = new PendingAppWidgetHostView(this, item.restoreStatus); item.hostView.updateAppWidget(null); + item.hostView.setOnClickListener(this); } item.hostView.setTag(item); @@ -4428,6 +4529,29 @@ public class Launcher extends Activity } } + /** + * Restores a pending widget. + * + * @param appWidgetId The app widget id + * @param cellInfo The position on screen where to create the widget. + */ + private void completeRestoreAppWidget(final int appWidgetId) { + LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId); + if ((view == null) || !(view instanceof PendingAppWidgetHostView)) { + Log.e(TAG, "Widget update called, when the widget no longer exists."); + return; + } + + PendingAppWidgetHostView pendingView = (PendingAppWidgetHostView) view; + pendingView.setStatus(LauncherAppWidgetInfo.RESTORE_COMPLETED); + + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) pendingView.getTag(); + info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; + + mWorkspace.reinflateWidgetsIfNecessary(); + LauncherModel.updateItemInDatabase(this, info); + } + public void onPageBoundSynchronously(int page) { mSynchronouslyBoundPages.add(page); } diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index 7eb005255..e39727b17 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -18,7 +18,6 @@ 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; @@ -32,36 +31,22 @@ import com.android.launcher3.DragLayer.TouchCompleteListener; * {@inheritDoc} */ public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener { + + LayoutInflater mInflater; + private CheckLongPressHelper mLongPressHelper; - private LayoutInflater mInflater; private Context mContext; private int mPreviousOrientation; private DragLayer mDragLayer; 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 @@ -70,22 +55,14 @@ 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; super.updateAppWidget(remoteViews); } - public boolean orientationChangedSincedInflation() { + public boolean isReinflateRequired() { + // Re-inflate is required if the orientation has changed since last inflated. int orientation = mContext.getResources().getConfiguration().orientation; if (mPreviousOrientation != orientation) { return true; diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index b3ac12b37..c1535abae 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -33,12 +33,17 @@ public class LauncherAppWidgetInfo extends ItemInfo { /** * This is set during the package backup creation. */ - public static final int RESTORE_REMAP_PENDING = 1; + public static final int FLAG_ID_NOT_VALID = 1; /** - * Widget provider is not yet installed. + * Indicates that the provider is not available yet. */ - public static final int RESTORE_PROVIDER_PENDING = 2; + public static final int FLAG_PROVIDER_NOT_READY = 2; + + /** + * Indicates that the widget UI is not yet ready, and user needs to set it up again. + */ + public static final int FLAG_UI_NOT_READY = 4; /** * Indicates that the widget hasn't been instantiated yet. @@ -89,6 +94,7 @@ public class LauncherAppWidgetInfo extends ItemInfo { super.onAddToDatabase(context, values); values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId); values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName.flattenToString()); + values.put(LauncherSettings.Favorites.RESTORED, restoreStatus); } /** @@ -121,6 +127,10 @@ public class LauncherAppWidgetInfo extends ItemInfo { } public final boolean isWidgetIdValid() { - return restoreStatus != RESTORE_REMAP_PENDING; + return (restoreStatus & FLAG_ID_NOT_VALID) == 0; + } + + public final boolean hasRestoreFlag(int flag) { + return (restoreStatus & flag) == flag; } } diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java index 5663314ea..aecf9b019 100644 --- a/src/com/android/launcher3/LauncherBackupHelper.java +++ b/src/com/android/launcher3/LauncherBackupHelper.java @@ -874,20 +874,25 @@ public class LauncherBackupHelper implements BackupHelper { values.put(Favorites.INTENT, favorite.intent); } values.put(Favorites.ITEM_TYPE, favorite.itemType); - if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) { - if (!TextUtils.isEmpty(favorite.appWidgetProvider)) { - values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider); - } - values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId); - } UserHandleCompat myUserHandle = UserHandleCompat.myUserHandle(); long userSerialNumber = UserManagerCompat.getInstance(mContext).getSerialNumberForUser(myUserHandle); values.put(LauncherSettings.Favorites.PROFILE_ID, userSerialNumber); - // Let LauncherModel know we've been here. - values.put(LauncherSettings.Favorites.RESTORED, 1); + if (favorite.itemType == Favorites.ITEM_TYPE_APPWIDGET) { + if (!TextUtils.isEmpty(favorite.appWidgetProvider)) { + values.put(Favorites.APPWIDGET_PROVIDER, favorite.appWidgetProvider); + } + values.put(Favorites.APPWIDGET_ID, favorite.appWidgetId); + values.put(LauncherSettings.Favorites.RESTORED, + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | + LauncherAppWidgetInfo.FLAG_UI_NOT_READY); + } else { + // Let LauncherModel know we've been here. + values.put(LauncherSettings.Favorites.RESTORED, 1); + } return values; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 86edaef44..5c668d65f 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -2128,47 +2128,59 @@ public class LauncherModel extends BroadcastReceiver int appWidgetId = c.getInt(appWidgetIdIndex); String savedProvider = c.getString(appWidgetProviderIndex); id = c.getLong(idIndex); + final ComponentName component = + ComponentName.unflattenFromString(savedProvider); 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) { + final boolean isIdValid = (restoreStatus & + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID) == 0; + + final boolean wasProviderReady = (restoreStatus & + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0; + + final AppWidgetProviderInfo provider = isIdValid + ? widgets.getAppWidgetInfo(appWidgetId) + : findAppWidgetProviderInfoWithComponent(context, component); + + final boolean isProviderReady = isValidProvider(provider); + if (!isSafeMode && wasProviderReady && !isProviderReady) { String log = "Deleting widget that isn't installed anymore: " - + "id=" + id + " appWidgetId=" + appWidgetId; + + "id=" + id + " appWidgetId=" + appWidgetId; Log.e(TAG, log); Launcher.addDumpLog(TAG, log, false); itemsToRemove.add(id); } else { - if (providerValid) { + if (isProviderReady) { appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, provider.provider); int[] minSpan = Launcher.getMinSpanForWidget(context, provider); appWidgetInfo.minSpanX = minSpan[0]; appWidgetInfo.minSpanY = minSpan[1]; + + int status = restoreStatus; + if (!wasProviderReady) { + // If provider was not previously ready, update the + // status and UI flag. + + // Id would be valid only if the widget restore broadcast was received. + if (isIdValid) { + status = LauncherAppWidgetInfo.RESTORE_COMPLETED; + } else { + status &= ~LauncherAppWidgetInfo + .FLAG_PROVIDER_NOT_READY; + } + } + appWidgetInfo.restoreStatus = status; } else { Log.v(TAG, "Widget restore pending id=" + id + " appWidgetId=" + appWidgetId + " status =" + restoreStatus); appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, - ComponentName.unflattenFromString(savedProvider)); + component); appWidgetInfo.restoreStatus = restoreStatus; } + appWidgetInfo.id = id; appWidgetInfo.screenId = c.getInt(screenIndex); appWidgetInfo.cellX = c.getInt(cellXIndex); @@ -2195,15 +2207,15 @@ public class LauncherModel extends BroadcastReceiver break; } - if (providerValid) { + if (isProviderReady) { String providerName = provider.provider.flattenToString(); - - if (!providerName.equals(savedProvider) || providerPending) { + if (!providerName.equals(savedProvider) || + (appWidgetInfo.restoreStatus != restoreStatus)) { ContentValues values = new ContentValues(); values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, providerName); values.put(LauncherSettings.Favorites.RESTORED, - LauncherAppWidgetInfo.RESTORE_COMPLETED); + appWidgetInfo.restoreStatus); String where = BaseColumns._ID + "= ?"; String[] args = {Long.toString(id)}; contentResolver.update(contentUri, values, where, args); diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java new file mode 100644 index 000000000..048e9f8c3 --- /dev/null +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -0,0 +1,78 @@ +package com.android.launcher3; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.TextView; + +public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implements OnClickListener { + + int mRestoreStatus; + + private TextView mDefaultView; + private OnClickListener mClickListener; + + public PendingAppWidgetHostView(Context context, int restoreStatus) { + super(context); + mRestoreStatus = restoreStatus; + } + + @Override + public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth, + int maxHeight) { + // No-op + } + + @Override + protected View getDefaultView() { + if (mDefaultView == null) { + mDefaultView = (TextView) mInflater.inflate(R.layout.appwidget_not_ready, this, false); + mDefaultView.setOnClickListener(this); + applyState(); + } + return mDefaultView; + } + + @Override + public void setOnClickListener(OnClickListener l) { + mClickListener = l; + } + + public void setStatus(int status) { + if (mRestoreStatus != status) { + mRestoreStatus = status; + applyState(); + } + } + + @Override + public boolean isReinflateRequired() { + // Re inflate is required if the the widget is restored. + return mRestoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED; + } + + private void applyState() { + if (mDefaultView != null) { + if (isReadyForClickSetup()) { + mDefaultView.setText(R.string.gadget_setup_text); + } else { + mDefaultView.setText(R.string.gadget_pending_text); + } + } + } + + @Override + public void onClick(View v) { + // AppWidgetHostView blocks all click events on the root view. Instead handle click events + // on the content and pass it along. + if (mClickListener != null) { + mClickListener.onClick(this); + } + } + + public boolean isReadyForClickSetup() { + return (mRestoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0 + && (mRestoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0; + } +} diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7c8708b50..f9fc14bb8 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -30,8 +30,6 @@ 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; @@ -49,7 +47,6 @@ 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; @@ -75,6 +72,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; /** * The workspace is a wide area with a wallpaper and a finite number of pages. @@ -1119,10 +1117,10 @@ public class Workspace extends SmoothPagedView for (int j = 0; j < itemCount; j++) { View v = swc.getChildAt(j); - if (v.getTag() instanceof LauncherAppWidgetInfo) { + if (v != null && v.getTag() instanceof LauncherAppWidgetInfo) { LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) v.getTag(); LauncherAppWidgetHostView lahv = (LauncherAppWidgetHostView) info.hostView; - if (lahv != null && lahv.orientationChangedSincedInflation()) { + if (lahv != null && lahv.isReinflateRequired()) { mLauncher.removeAppWidget(info); // Remove the current widget which is inflated with the wrong orientation cl.removeView(lahv); @@ -3258,7 +3256,6 @@ public class Workspace extends SmoothPagedView LauncherAppState app = LauncherAppState.getInstance(); DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); - Resources res = launcher.getResources(); Display display = launcher.getWindowManager().getDefaultDisplay(); Point smallestSize = new Point(); Point largestSize = new Point(); @@ -4591,29 +4588,43 @@ public class Workspace extends SmoothPagedView } public Folder getFolderForTag(final Object tag) { - final Folder[] value = new Folder[1]; - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + return (Folder) getFirstMatch(new ItemOperator() { + @Override public boolean evaluate(ItemInfo info, View v, View parent) { - if (v instanceof Folder) { - Folder f = (Folder) v; - if (f.getInfo() == tag && f.getInfo().opened) { - value[0] = f; - return true; - } - } - return false; + return (v instanceof Folder) && (((Folder) v).getInfo() == tag) + && ((Folder) v).getInfo().opened; } }); - return value[0]; } public View getViewForTag(final Object tag) { + return getFirstMatch(new ItemOperator() { + + @Override + public boolean evaluate(ItemInfo info, View v, View parent) { + return info == tag; + } + }); + } + + public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) { + return (LauncherAppWidgetHostView) getFirstMatch(new ItemOperator() { + + @Override + public boolean evaluate(ItemInfo info, View v, View parent) { + return (info instanceof LauncherAppWidgetInfo) && + ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId; + } + }); + } + + private View getFirstMatch(final ItemOperator operator) { final View[] value = new View[1]; mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v, View parent) { - if (v.getTag() == tag) { + if (operator.evaluate(info, v, parent)) { value[0] = v; return true; } @@ -4895,7 +4906,7 @@ public class Workspace extends SmoothPagedView } private void restorePendingWidgets(final Set installedPackaged) { - final ContentResolver contentResolver = getContext().getContentResolver(); + final AtomicBoolean widgetsChanged = new AtomicBoolean(false); // Iterate non recursively as widgets can't be inside a folder. mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @@ -4903,22 +4914,21 @@ public class Workspace extends SmoothPagedView public boolean evaluate(ItemInfo info, View v, View parent) { if (info instanceof LauncherAppWidgetInfo) { LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; - if (widgetInfo.restoreStatus == LauncherAppWidgetInfo.RESTORE_PROVIDER_PENDING + if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && 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); + widgetsChanged.set(true); } } // process all the widget return false; } }); + if (widgetsChanged.get()) { + // Reload layout and update widget status + // TODO instead of full reload, just update the specific widgets + getContext().getContentResolver() + .notifyChange(LauncherSettings.Favorites.CONTENT_URI, null); + } } private void moveToScreen(int page, boolean animate) { -- 2.11.0