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
This commit is contained in:
Sunny Goyal
2014-08-11 17:05:23 -07:00
parent 0fe505bf82
commit 0fc1be164e
20 changed files with 317 additions and 99 deletions
+7 -3
View File
@@ -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"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

-24
View File
@@ -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>
+4 -12
View File
@@ -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" />
-1
View File
@@ -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>
+4
View File
@@ -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>
+1 -4
View File
@@ -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>
+57 -15
View File
@@ -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);
}
}
+4 -8
View File
@@ -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();
@@ -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();
}
}
}
@@ -67,6 +67,11 @@ public class LauncherAppWidgetInfo extends ItemInfo {
*/
int restoreStatus;
/**
* Indicates the installation progress of the widget provider
*/
int installProgress;
private boolean mHasNotifiedInitialWidgetSizeChanged;
/**
@@ -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);
}
}
+2 -2
View File
@@ -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();
@@ -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);
}
}
}
+79 -8
View File
@@ -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);
}
}
}
}
}