From 0fc1be164e982433e619bcbb16aa67e28ff681ef Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 11 Aug 2014 17:05:23 -0700 Subject: [PATCH] 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 --- AndroidManifest.xml | 10 +- res/drawable-hdpi/widget_container_holo.9.png | Bin 369 -> 0 bytes res/drawable-mdpi/widget_container_holo.9.png | Bin 285 -> 0 bytes .../widget_container_holo.9.png | Bin 460 -> 0 bytes res/drawable-xxhdpi/bg_preloader.png | Bin 3785 -> 0 bytes res/drawable-xxhdpi/bg_preloader_progress.png | Bin 1131 -> 0 bytes .../widget_container_holo.9.png | Bin 1558 -> 0 bytes res/drawable/bg_appwidget_not_ready.xml | 24 --- res/layout/appwidget_not_ready.xml | 16 +- res/values/colors.xml | 1 - res/values/config.xml | 4 + res/values/strings.xml | 5 +- src/com/android/launcher3/IconCache.java | 72 ++++++-- src/com/android/launcher3/Launcher.java | 12 +- .../launcher3/LauncherAppWidgetHost.java | 16 ++ .../launcher3/LauncherAppWidgetInfo.java | 5 + .../launcher3/LauncherBackupHelper.java | 3 + src/com/android/launcher3/LauncherModel.java | 4 +- .../launcher3/PendingAppWidgetHostView.java | 157 +++++++++++++++--- src/com/android/launcher3/Workspace.java | 87 +++++++++- 20 files changed, 317 insertions(+), 99 deletions(-) delete mode 100644 res/drawable-hdpi/widget_container_holo.9.png delete mode 100644 res/drawable-mdpi/widget_container_holo.9.png delete mode 100644 res/drawable-xhdpi/widget_container_holo.9.png delete mode 100644 res/drawable-xxhdpi/bg_preloader.png delete mode 100644 res/drawable-xxhdpi/bg_preloader_progress.png delete mode 100644 res/drawable-xxhdpi/widget_container_holo.9.png delete mode 100644 res/drawable/bg_appwidget_not_ready.xml diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 47e9368a2c..3633c8c961 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -72,11 +72,15 @@ + android:restoreAnyVersion="true" + android:supportsRtl="true" > + KmBCJfKoExi!3`3%3GpF( zihAz3cixT{@ajAG1SXzHxCvqLunX({UbJZ!HHBJ|ntllvX4r2t%fE_S&r@hEZyMH z9!oR}XVD3Yy!UeG==I(YIyn?kYrW{YE}43JTWb@o^#Z|-6OIX2Rn=45wri!73X}Ih zoO7=4`v(ASC-6=3C4gH1@elFeAb<`)1;CE{_QVj95ZS5FUyY5;q9pMFaK(UaxJnG6 P00000NkvXXu0mjf*WjJ^ diff --git a/res/drawable-mdpi/widget_container_holo.9.png b/res/drawable-mdpi/widget_container_holo.9.png deleted file mode 100644 index db24457d68673acb294c08b65fed40c91964f93f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285 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 czL{H9e@$KC>fG_0`+rF>!6T|EcL5JtI za_T%@dMx**+LP`%ts;B8COwL&nbXSJQXjZv`qwKTCvu5hT(&J%{^H?pX4h(6l}umn zX>+&h<# zqwsxm_Nv2*q7VNv_J-#LExfl~;mgc-_F?tW6Y92?v1w#1UR@U}uzK~peeZ5sJMwrW z_AhY1p!`Dc%Psa41=aA_^Pjh`QGap$Hvf-p3+87h*6%-A{zd)ezlYtSQ75~5{{9P$ z3W;BQu0(Kdve|E^&x+M=xBWS^o9%jScyc4h?LCus@dvUfCH;5Z>hd+`VdunOrfdE^ z=+r&3Jp8-n)=kE$O}`R%{+VIO#zg9|kyU%D;$>eP2G*>#qSxaq1n%CpicpB`DTUFgCD;mEHmgk9bmFH19I zULf%7nMbJc)pm=hmB)55T}go@kvZo6mAS=`EJw&T*HhXP*Hq$hQA%xHeqqn;tob$tTAnwMoQu!Qwv))ZXbU9CABs-(eWY utZTKaI`qyCFCJa5ja@hYcYgT*1pJFiE?J6tcm4uqe+ExiKbLh*2~7ZFlQuyB diff --git a/res/drawable-xxhdpi/widget_container_holo.9.png b/res/drawable-xxhdpi/widget_container_holo.9.png deleted file mode 100644 index 8f79920c441419144d8bec794c4119427ce94a26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1558 zcmbVMdr%a09A8RKMqoPe8G-C9W*Xk@)CXlcJK}a|+k@$iw53wLESqZFWYdGErUH_sF0=a+@ z^66cOD`H_ooTEu(Jx!%ve^afW3_yj?fCX_%76`Kv1IEL3e3XhCp>bVGo-5lh1dc$Hh+0%O=gd;B3ePKR2ktjK3aGZ(2s1||4VI{6{bX0P)|GM!&bky4zVPQ8L z6&ggpT#sOGf-L9m;f55T>YgLMH}D(l+|W3*l+`_$1oHp zP?R*7^k%|@;kcE+^?EX)@gOcrBRT}nr%95)QPhf>P{d*-3?xb-7-rKW39gfmN(}F3 z6LvY-?gZEJP%dQ=Sw<2>uOQSVI-o2hNJ2CuL_mv254spX$17{4JolodS&>`825h1b z2FLSDaSyPsGs|6L#c|S#Axhi?VZqI)1u^Mp(m-1b&;&Q|e{u%P&cI4@{HIwGTXF{~ z)BCM2FYX^6mX}YAC?AcyF4zVDkJy~F$r~RUYd_XDgiLR_LbRvzDLC01H=a>Bwuzb_VAYZ&elhF4>8Z8+%u5Sw@#n zAMR;aR|_@7x?f7a??+8Tb(asd#b>lFQ>{-0OEoI8JT+6SDw+4@{!sp1^(&VaE%;$q zaeZTleaWU5-#Ia|`1I&aUpacMcg#nb)mMJK!xLGnyhRNs-#c45a&Y!jGgT%qQq`Jt zv;KIXr=q#zSg`Q+ezjw3ZpML}O-DWi`h&YFxvbRMozv78ZYP6u-vu90w6Eae75m!8 z@Cz;Kmr_@HXj9wgC3%_MbDg1!`R<_|gFh`jC8anvZX4Av9NtZ~E_j<$JBzmtQ|g=# zrtV8`*Y4J+Mb$C8EyP*F?a9b z8wH_(8K#kW>EC4FeCEe#=lDHO_>USX&zDu#_qdBTKbBV-o@(4aqp~(R-B6#^H}L7$ zWR9BDPnX}^b$YbfY>&lVAo{Fm2fgo(Lj2*3uV#JnerNX|`~QO1f`^j&`z9Zqbo~O< zm389v^vjFDZ9vmOyLXjtW$(9}ha+Vv+l$lG%hyVT&oO&%&NKirUp-7!pMTYRVB?+c z63gIV_R?)0TbcE>imuOeDQo^rcAU)_I9`9^mVI>+Fys;D_nOeC@~3uMOX)82^DqAc DupKLC diff --git a/res/drawable/bg_appwidget_not_ready.xml b/res/drawable/bg_appwidget_not_ready.xml deleted file mode 100644 index a8b56c2f85..0000000000 --- a/res/drawable/bg_appwidget_not_ready.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/appwidget_not_ready.xml b/res/layout/appwidget_not_ready.xml index f5f2aab213..be7c33b361 100644 --- a/res/layout/appwidget_not_ready.xml +++ b/res/layout/appwidget_not_ready.xml @@ -1,5 +1,6 @@ - - + android:layout_height="match_parent" /> diff --git a/res/values/colors.xml b/res/values/colors.xml index 41f38921f1..4e64d41846 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -26,7 +26,6 @@ #20000000 #FCCC - #F48F #CCFFFFFF #A0000000 diff --git a/res/values/config.xml b/res/values/config.xml index a16f265a89..750a6aa72a 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -17,6 +17,10 @@ 21 + + false + -1500 diff --git a/res/values/strings.xml b/res/values/strings.xml index 286e04fe54..aa5f7b626e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -193,11 +193,8 @@ s --> Problem loading widget - - Widget not ready - - Setup widget + Setup This is a system app and can\'t be uninstalled. diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 221df583b7..06b77756de 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 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 labelCache, UserHandleCompat user) { + HashMap 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 getAllIcons() { synchronized (mCache) { HashMap set = new HashMap(); @@ -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 5eedc8a3ab..062e848385 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 fa5e38f8b8..a309f268c8 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 mProviderChangeListeners = new ArrayList(); + 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 c1535abaee..47554824f9 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 aecf9b0190..64e82c7540 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 109a70090d..4c9d1a700d 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 widgets = AppWidgetManager.getInstance(context).getInstalledProviders(); diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index 048e9f8c39..04014366c4 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; - public PendingAppWidgetHostView(Context context, int restoreStatus) { + private Bitmap mIcon; + private PreloadIconDrawable mDrawable; + + private Drawable mCenterDrawable; + private Drawable mTopCornerDrawable; + + private boolean mDrawableSizeChanged; + + 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 + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mDrawableSizeChanged = true; + } + + 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()) { + mCenterDrawable = getResources().getDrawable(R.drawable.ic_setting); + mTopCornerDrawable = new FastBitmapDrawable(mIcon); + } else { + 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 - public boolean isReinflateRequired() { - // Re inflate is required if the the widget is restored. - return mRestoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED; + protected boolean verifyDrawable(Drawable who) { + return (who == mDrawable) || super.verifyDrawable(who); } - private void applyState() { - if (mDefaultView != null) { - if (isReadyForClickSetup()) { - mDefaultView.setText(R.string.gadget_setup_text); - } else { - mDefaultView.setText(R.string.gadget_pending_text); - } + 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 cd6fcca1e2..8b3a514966 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 installedPackaged) { - final AtomicBoolean widgetsChanged = new AtomicBoolean(false); + final ArrayList changedInfo = new ArrayList(); + // 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 mInfos; + private final LauncherAppWidgetHost mHost; + private final Handler mHandler; + + private boolean mRefreshPending; + + public DeferredWidgetRefresh(ArrayList 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); + } + } + } + } }