diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2014-08-11 17:05:23 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2014-08-12 16:00:03 -0700 |
commit | 0fc1be164e982433e619bcbb16aa67e28ff681ef (patch) | |
tree | b72e41365906b7619407950d41375afb6eed5dc3 | |
parent | 0fe505bf82a265e51c556d7204976651cde7f55c (diff) | |
download | android_packages_apps_Trebuchet-0fc1be164e982433e619bcbb16aa67e28ff681ef.tar.gz android_packages_apps_Trebuchet-0fc1be164e982433e619bcbb16aa67e28ff681ef.tar.bz2 android_packages_apps_Trebuchet-0fc1be164e982433e619bcbb16aa67e28ff681ef.zip |
Updating the ui for widget restore flow
> Pending widget show a PreloadIconDrawable to indicate
installation progress
> Only the concerned widgets are reinflated on package
install and not the whole workspace.
> Adding support for storing default package icon in
IconCache
issue: 10779035
issue: 16737660
Change-Id: Id787ae4a5ef72d6e01aeb5a1bae5ab8840037679
20 files changed, 315 insertions, 97 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 47e9368a2..3633c8c96 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -72,11 +72,15 @@ <application android:name="com.android.launcher3.LauncherApplication" - android:label="@string/application_name" - android:icon="@mipmap/ic_launcher_home" + android:allowBackup="@bool/enable_backup" + android:backupAgent="com.android.launcher3.LauncherBackupAgentHelper" android:hardwareAccelerated="true" + android:icon="@mipmap/ic_launcher_home" + android:label="@string/application_name" android:largeHeap="@bool/config_largeHeap" - android:supportsRtl="true"> + android:restoreAnyVersion="true" + android:supportsRtl="true" > + <activity android:name="com.android.launcher3.Launcher" android:launchMode="singleTask" diff --git a/res/drawable-hdpi/widget_container_holo.9.png b/res/drawable-hdpi/widget_container_holo.9.png Binary files differdeleted file mode 100644 index 8c15a7c86..000000000 --- a/res/drawable-hdpi/widget_container_holo.9.png +++ /dev/null diff --git a/res/drawable-mdpi/widget_container_holo.9.png b/res/drawable-mdpi/widget_container_holo.9.png Binary files differdeleted file mode 100644 index db24457d6..000000000 --- a/res/drawable-mdpi/widget_container_holo.9.png +++ /dev/null diff --git a/res/drawable-xhdpi/widget_container_holo.9.png b/res/drawable-xhdpi/widget_container_holo.9.png Binary files differdeleted file mode 100644 index 1313fe70d..000000000 --- a/res/drawable-xhdpi/widget_container_holo.9.png +++ /dev/null diff --git a/res/drawable-xxhdpi/bg_preloader.png b/res/drawable-xxhdpi/bg_preloader.png Binary files differdeleted file mode 100644 index 56b80607d..000000000 --- a/res/drawable-xxhdpi/bg_preloader.png +++ /dev/null diff --git a/res/drawable-xxhdpi/bg_preloader_progress.png b/res/drawable-xxhdpi/bg_preloader_progress.png Binary files differdeleted file mode 100644 index 443afe936..000000000 --- a/res/drawable-xxhdpi/bg_preloader_progress.png +++ /dev/null diff --git a/res/drawable-xxhdpi/widget_container_holo.9.png b/res/drawable-xxhdpi/widget_container_holo.9.png Binary files differdeleted file mode 100644 index 8f79920c4..000000000 --- a/res/drawable-xxhdpi/widget_container_holo.9.png +++ /dev/null diff --git a/res/drawable/bg_appwidget_not_ready.xml b/res/drawable/bg_appwidget_not_ready.xml deleted file mode 100644 index a8b56c2f8..000000000 --- a/res/drawable/bg_appwidget_not_ready.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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 index f5f2aab21..be7c33b36 100644 --- a/res/layout/appwidget_not_ready.xml +++ b/res/layout/appwidget_not_ready.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2009 The Android Open Source Project +<!-- + 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. @@ -14,15 +15,6 @@ limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" +<View 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" - /> + android:layout_height="match_parent" /> diff --git a/res/values/colors.xml b/res/values/colors.xml index 41f38921f..4e64d4184 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -26,7 +26,6 @@ <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/config.xml b/res/values/config.xml index a16f265a8..750a6aa72 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -17,6 +17,10 @@ <!-- Max number of page indicators to show --> <integer name="config_maxNumberOfPageIndicatorsToShow">21</integer> + <!-- App data backup and restore. To enble backup, register with an android backup service. + http://developer.android.com/guide/topics/data/backup.html#BackupKey --> + <bool name="enable_backup">false</bool> + <!-- DragController --> <integer name="config_flingToDeleteMinVelocity">-1500</integer> diff --git a/res/values/strings.xml b/res/values/strings.xml index 286e04fe5..aa5f7b626 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -193,11 +193,8 @@ 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. --> - <string name="gadget_pending_text" translatable="false">Widget not ready</string> - <!-- Text to show user in place of a gadget when it is not yet initialized. --> - <string name="gadget_setup_text" translatable="false">Setup widget</string> + <string name="gadget_setup_text">Setup</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/IconCache.java b/src/com/android/launcher3/IconCache.java index 221df583b..06b77756d 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -16,22 +16,20 @@ package com.android.launcher3; -import com.android.launcher3.backup.BackupProtos; - import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; -import android.graphics.Paint; import android.graphics.drawable.Drawable; -import android.os.Build; import android.util.Log; import com.android.launcher3.compat.LauncherActivityInfoCompat; @@ -54,12 +52,15 @@ import java.util.Map.Entry; * Cache of application icons. Icons can be made from any thread. */ public class IconCache { - @SuppressWarnings("unused") + private static final String TAG = "Launcher.IconCache"; private static final int INITIAL_ICON_CACHE_CAPACITY = 50; private static final String RESOURCE_FILE_PREFIX = "icon_"; + // Empty class name is used for storing package default entry. + private static final String EMPTY_CLASS_NAME = "."; + private static final boolean DEBUG = true; private static class CacheEntry { @@ -237,7 +238,7 @@ public class IconCache { HashMap<Object, CharSequence> labelCache) { synchronized (mCache) { CacheEntry entry = cacheLocked(application.componentName, info, labelCache, - info.getUser()); + info.getUser(), false); application.title = entry.title; application.iconBitmap = entry.icon; @@ -246,10 +247,10 @@ public class IconCache { } public Bitmap getIcon(Intent intent, UserHandleCompat user) { - return getIcon(intent, null, user); + return getIcon(intent, null, user, true); } - public Bitmap getIcon(Intent intent, String title, UserHandleCompat user) { + public Bitmap getIcon(Intent intent, String title, UserHandleCompat user, boolean usePkgIcon) { synchronized (mCache) { LauncherActivityInfoCompat launcherActInfo = mLauncherApps.resolveActivity(intent, user); @@ -257,11 +258,11 @@ public class IconCache { // null info means not installed, but if we have a component from the intent then // we should still look in the cache for restored app icons. - if (launcherActInfo == null && component == null) { + if (component == null) { return getDefaultIcon(user); } - CacheEntry entry = cacheLocked(component, launcherActInfo, null, user); + CacheEntry entry = cacheLocked(component, launcherActInfo, null, user, usePkgIcon); if (title != null) { entry.title = title; entry.contentDescription = mUserManager.getBadgedLabelForUser(title, user); @@ -284,7 +285,7 @@ public class IconCache { return null; } - CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser()); + CacheEntry entry = cacheLocked(component, info, labelCache, info.getUser(), false); return entry.icon; } } @@ -294,7 +295,7 @@ public class IconCache { } private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info, - HashMap<Object, CharSequence> labelCache, UserHandleCompat user) { + HashMap<Object, CharSequence> labelCache, UserHandleCompat user, boolean usePackageIcon) { CacheKey cacheKey = new CacheKey(componentName, user); CacheEntry entry = mCache.get(cacheKey); if (entry == null) { @@ -324,15 +325,52 @@ public class IconCache { componentName.toShortString()); entry.icon = preloaded; } else { - if (DEBUG) Log.d(TAG, "using default icon for " + - componentName.toShortString()); - entry.icon = getDefaultIcon(user); + if (usePackageIcon) { + CacheEntry packageEntry = getEntryForPackage( + componentName.getPackageName(), user); + if (packageEntry != null && packageEntry.icon != null) { + if (DEBUG) Log.d(TAG, "using package default icon for " + + componentName.toShortString()); + entry.icon = packageEntry.icon; + } + } + if (entry.icon == null) { + if (DEBUG) Log.d(TAG, "using default icon for " + + componentName.toShortString()); + entry.icon = getDefaultIcon(user); + } } } } return entry; } + /** + * Gets an entry for the package, which can be used as a fallback entry for various components. + */ + private CacheEntry getEntryForPackage(String packageName, UserHandleCompat user) { + ComponentName cn = getPackageComponent(packageName); + CacheKey cacheKey = new CacheKey(cn, user); + CacheEntry entry = mCache.get(cacheKey); + if (entry == null) { + entry = new CacheEntry(); + mCache.put(cacheKey, entry); + + try { + ApplicationInfo info = mPackageManager.getApplicationInfo(packageName, 0); + entry.title = info.loadLabel(mPackageManager); + entry.icon = Utilities.createIconBitmap(info.loadIcon(mPackageManager), mContext); + } catch (NameNotFoundException e) { + if (DEBUG) Log.d(TAG, "Application not installed " + packageName); + } + + if (entry.icon == null) { + entry.icon = getPreloadedIcon(cn, user); + } + } + return entry; + } + public HashMap<ComponentName,Bitmap> getAllIcons() { synchronized (mCache) { HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>(); @@ -471,4 +509,8 @@ public class IconCache { String filename = resourceName.replace(File.separatorChar, '_'); return RESOURCE_FILE_PREFIX + filename; } + + static ComponentName getPackageComponent(String packageName) { + return new ComponentName(packageName, EMPTY_CLASS_NAME); + } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5eedc8a3a..062e84838 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -23,7 +23,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityManager; @@ -90,10 +89,8 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener; 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; @@ -4445,7 +4442,9 @@ public class Launcher extends Activity item.hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo); } else { appWidgetInfo = null; - item.hostView = new PendingAppWidgetHostView(this, item.restoreStatus); + PendingAppWidgetHostView view = new PendingAppWidgetHostView(this, item); + view.updateIcon(mIconCache); + item.hostView = view; item.hostView.updateAppWidget(null); item.hostView.setOnClickListener(this); } @@ -4478,10 +4477,7 @@ public class Launcher extends Activity return; } - PendingAppWidgetHostView pendingView = (PendingAppWidgetHostView) view; - pendingView.setStatus(LauncherAppWidgetInfo.RESTORE_COMPLETED); - - LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) pendingView.getTag(); + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag(); info.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED; mWorkspace.reinflateWidgetsIfNecessary(); diff --git a/src/com/android/launcher3/LauncherAppWidgetHost.java b/src/com/android/launcher3/LauncherAppWidgetHost.java index fa5e38f8b..a309f268c 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHost.java +++ b/src/com/android/launcher3/LauncherAppWidgetHost.java @@ -22,6 +22,8 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.os.TransactionTooLargeException; +import java.util.ArrayList; + /** * Specific {@link AppWidgetHost} that creates our {@link LauncherAppWidgetHostView} * which correctly captures all long-press events. This ensures that users can @@ -29,6 +31,8 @@ import android.os.TransactionTooLargeException; */ public class LauncherAppWidgetHost extends AppWidgetHost { + private final ArrayList<Runnable> mProviderChangeListeners = new ArrayList<Runnable>(); + Launcher mLauncher; public LauncherAppWidgetHost(Launcher launcher, int hostId) { @@ -64,9 +68,21 @@ public class LauncherAppWidgetHost extends AppWidgetHost { clearViews(); } + public void addProviderChangeListener(Runnable callback) { + mProviderChangeListeners.add(callback); + } + + public void removeProviderChangeListener(Runnable callback) { + mProviderChangeListeners.remove(callback); + } + protected void onProvidersChanged() { // Once we get the message that widget packages are updated, we need to rebind items // in AppsCustomize accordingly. mLauncher.bindPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(mLauncher)); + + for (Runnable callback : mProviderChangeListeners) { + callback.run(); + } } } diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index c1535abae..47554824f 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -67,6 +67,11 @@ public class LauncherAppWidgetInfo extends ItemInfo { */ int restoreStatus; + /** + * Indicates the installation progress of the widget provider + */ + int installProgress; + private boolean mHasNotifiedInitialWidgetSizeChanged; /** diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java index aecf9b019..64e82c754 100644 --- a/src/com/android/launcher3/LauncherBackupHelper.java +++ b/src/com/android/launcher3/LauncherBackupHelper.java @@ -692,6 +692,9 @@ public class LauncherBackupHelper implements BackupHelper { .decodeByteArray(widget.icon.data, 0, widget.icon.data.length); if (icon == null) { Log.w(TAG, "failed to unpack widget icon for " + key.name); + } else { + IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(widget.provider), + icon, widget.icon.dpi); } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 109a70090..4c9d1a700 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -3089,7 +3089,7 @@ public class LauncherModel extends BroadcastReceiver info.user = UserHandleCompat.myUserHandle(); info.contentDescription = mUserManager.getBadgedLabelForUser( info.title.toString(), info.user); - info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user)); + info.setIcon(mIconCache.getIcon(intent, info.title.toString(), info.user, false)); info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; info.restoredIntent = intent; info.wasPromise = true; @@ -3378,7 +3378,7 @@ public class LauncherModel extends BroadcastReceiver /** * Attempts to find an AppWidgetProviderInfo that matches the given component. */ - AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, + static AppWidgetProviderInfo findAppWidgetProviderInfoWithComponent(Context context, ComponentName component) { List<AppWidgetProviderInfo> widgets = AppWidgetManager.getInstance(context).getInstalledProviders(); diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index 048e9f8c3..04014366c 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -1,21 +1,59 @@ +/* + * 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; import android.content.Context; +import android.content.Intent; +import android.content.res.Resources.Theme; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; 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 static Theme sPreloaderTheme; - private TextView mDefaultView; + private final Rect mRect = new Rect(); + private View mDefaultView; private OnClickListener mClickListener; + private final LauncherAppWidgetInfo mInfo; + private final int mStartState; + private final Intent mIconLookupIntent; + + private Bitmap mIcon; + private PreloadIconDrawable mDrawable; + + private Drawable mCenterDrawable; + private Drawable mTopCornerDrawable; + + private boolean mDrawableSizeChanged; - public PendingAppWidgetHostView(Context context, int restoreStatus) { + public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info) { super(context); - mRestoreStatus = restoreStatus; + mInfo = info; + mStartState = info.restoreStatus; + mIconLookupIntent = new Intent().setComponent(info.providerName); + + setBackgroundResource(R.drawable.quantum_panel_dark); + setWillNotDraw(false); } @Override @@ -27,7 +65,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen @Override protected View getDefaultView() { if (mDefaultView == null) { - mDefaultView = (TextView) mInflater.inflate(R.layout.appwidget_not_ready, this, false); + mDefaultView = mInflater.inflate(R.layout.appwidget_not_ready, this, false); mDefaultView.setOnClickListener(this); applyState(); } @@ -39,26 +77,57 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen mClickListener = l; } - public void setStatus(int status) { - if (mRestoreStatus != status) { - mRestoreStatus = status; - applyState(); - } + @Override + public boolean isReinflateRequired() { + // Re inflate is required any time the widget restore status changes + return mStartState != mInfo.restoreStatus; } @Override - public boolean isReinflateRequired() { - // Re inflate is required if the the widget is restored. - return mRestoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED; + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mDrawableSizeChanged = true; } - private void applyState() { - if (mDefaultView != null) { + public void updateIcon(IconCache cache) { + Bitmap icon = cache.getIcon(mIconLookupIntent, mInfo.user); + if (mIcon == icon) { + return; + } + mIcon = icon; + if (mDrawable != null) { + mDrawable.setCallback(null); + mDrawable = null; + } + if (mIcon != null) { + // The view displays two modes, one with a setup icon and another with a preload icon + // in the center. if (isReadyForClickSetup()) { - mDefaultView.setText(R.string.gadget_setup_text); + mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting); + mTopCornerDrawable = new FastBitmapDrawable(mIcon); } else { - mDefaultView.setText(R.string.gadget_pending_text); + if (sPreloaderTheme == null) { + sPreloaderTheme = getResources().newTheme(); + sPreloaderTheme.applyStyle(R.style.PreloadIcon, true); + } + + FastBitmapDrawable drawable = Utilities.createIconDrawable(mIcon); + mDrawable = new PreloadIconDrawable(drawable, sPreloaderTheme); + mDrawable.setCallback(this); + applyState(); } + mDrawableSizeChanged = true; + } + } + + @Override + protected boolean verifyDrawable(Drawable who) { + return (who == mDrawable) || super.verifyDrawable(who); + } + + public void applyState() { + if (mDrawable != null) { + mDrawable.setLevel(mInfo.installProgress); } } @@ -72,7 +141,51 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen } public boolean isReadyForClickSetup() { - return (mRestoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0 - && (mRestoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0; + return (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) == 0 + && (mInfo.restoreStatus & LauncherAppWidgetInfo.FLAG_UI_NOT_READY) != 0; } + + @Override + protected void onDraw(Canvas canvas) { + if (mDrawable != null) { + if (mDrawableSizeChanged) { + int maxSize = LauncherAppState.getInstance().getDynamicGrid() + .getDeviceProfile().iconSizePx + 2 * mDrawable.getOutset(); + int size = Math.min(maxSize, Math.min( + getWidth() - getPaddingLeft() - getPaddingRight(), + getHeight() - getPaddingTop() - getPaddingBottom())); + + mRect.set(0, 0, size, size); + mRect.inset(mDrawable.getOutset(), mDrawable.getOutset()); + mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2); + mDrawable.setBounds(mRect); + mDrawableSizeChanged = false; + } + + mDrawable.draw(canvas); + } else if ((mCenterDrawable != null) && (mTopCornerDrawable != null)) { + if (mDrawableSizeChanged) { + int iconSize = getResources().getDimensionPixelSize(R.dimen.app_icon_size); + int paddingTop = getPaddingTop(); + int paddingLeft = getPaddingLeft(); + + int size = Math.min(iconSize, Math.min( + getWidth() - paddingLeft - getPaddingRight(), + getHeight() - paddingTop - getPaddingBottom())); + mRect.set(0, 0, size, size); + mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2); + mCenterDrawable.setBounds(mRect); + + size = Math.min(size / 2, + Math.max(mRect.top - paddingTop, mRect.left - paddingLeft)); + mTopCornerDrawable.setBounds(paddingLeft, paddingTop, + paddingLeft + size, paddingTop + size); + mDrawableSizeChanged = false; + } + + mCenterDrawable.draw(canvas); + mTopCornerDrawable.draw(canvas); + } + } + } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index cd6fcca1e..8b3a51496 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -27,6 +27,7 @@ import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.app.WallpaperManager; +import android.appwidget.AppWidgetHost; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; import android.content.ComponentName; @@ -45,7 +46,10 @@ import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; +import android.os.Handler; +import android.os.Handler.Callback; import android.os.IBinder; +import android.os.Message; import android.os.Parcelable; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; @@ -74,6 +78,7 @@ import java.util.Iterator; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; /** * The workspace is a wide area with a wallpaper and a finite number of pages. @@ -431,7 +436,6 @@ public class Workspace extends SmoothPagedView * Initializes various states for this workspace. */ protected void initWorkspace() { - Context context = getContext(); mCurrentPage = mDefaultPage; Launcher.setScreen(mCurrentPage); LauncherAppState app = LauncherAppState.getInstance(); @@ -4887,7 +4891,14 @@ public class Workspace extends SmoothPagedView ((ShortcutInfo) info).setProgress(installInfo.progress); ((ShortcutInfo) info).setState(installInfo.state); ((BubbleTextView)v).applyState(); + } else if (v instanceof PendingAppWidgetHostView + && info instanceof LauncherAppWidgetInfo + && ((LauncherAppWidgetInfo) info).providerName.getPackageName() + .equals(installInfo.packageName)) { + ((LauncherAppWidgetInfo) info).installProgress = installInfo.progress; + ((PendingAppWidgetHostView) v).applyState(); } + // process all the shortcuts return false; } @@ -4904,7 +4915,8 @@ public class Workspace extends SmoothPagedView } private void restorePendingWidgets(final Set<String> installedPackaged) { - final AtomicBoolean widgetsChanged = new AtomicBoolean(false); + final ArrayList<LauncherAppWidgetInfo> changedInfo = new ArrayList<LauncherAppWidgetInfo>(); + // Iterate non recursively as widgets can't be inside a folder. mapOverItems(MAP_NO_RECURSE, new ItemOperator() { @@ -4914,18 +4926,28 @@ public class Workspace extends SmoothPagedView LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info; if (widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && installedPackaged.contains(widgetInfo.providerName.getPackageName())) { - widgetsChanged.set(true); + + changedInfo.add(widgetInfo); + + // Remove the provider not ready flag + widgetInfo.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; + LauncherModel.updateItemInDatabase(getContext(), widgetInfo); } } // 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); + if (!changedInfo.isEmpty()) { + DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo, + mLauncher.getAppWidgetHost()); + if (LauncherModel.findAppWidgetProviderInfoWithComponent(getContext(), + changedInfo.get(0).providerName) != null) { + // Re-inflate the widgets which have changed status + widgetRefresh.run(); + } else { + // widgetRefresh will automatically run when the packages are updated. + } } } @@ -5003,4 +5025,53 @@ public class Workspace extends SmoothPagedView public void getLocationInDragLayer(int[] loc) { mLauncher.getDragLayer().getLocationInDragLayer(this, loc); } + + /** + * Used as a workaround to ensure that the AppWidgetService receives the + * PACKAGE_ADDED broadcast before updating widgets. + */ + private class DeferredWidgetRefresh implements Runnable { + private final ArrayList<LauncherAppWidgetInfo> mInfos; + private final LauncherAppWidgetHost mHost; + private final Handler mHandler; + + private boolean mRefreshPending; + + public DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos, + LauncherAppWidgetHost host) { + mInfos = infos; + mHost = host; + mHandler = new Handler(); + mRefreshPending = true; + + mHost.addProviderChangeListener(this); + // Force refresh after 10 seconds, if we don't get the provider changed event. + // This could happen when the provider is no longer available in the app. + mHandler.postDelayed(this, 10000); + } + + @Override + public void run() { + mHost.removeProviderChangeListener(this); + mHandler.removeCallbacks(this); + + if (!mRefreshPending) { + return; + } + + mRefreshPending = false; + + for (LauncherAppWidgetInfo info : mInfos) { + if (info.hostView instanceof PendingAppWidgetHostView) { + PendingAppWidgetHostView view = (PendingAppWidgetHostView) info.hostView; + mLauncher.removeAppWidget(info); + + CellLayout cl = (CellLayout) view.getParent().getParent(); + // Remove the current widget + cl.removeView(view); + mLauncher.bindAppWidget(info); + } + } + } + } } |