();
+ for (ComponentName componentName: mCache.keySet()) {
+ if (componentName.getPackageName().equals(packageName)) {
+ forDeletion.add(componentName);
+ }
+ }
+ for (ComponentName condemned: forDeletion) {
+ remove(condemned);
+ }
+ }
+
/**
* Empty out the cache.
*/
@@ -177,15 +208,22 @@ public class IconCache {
}
public Bitmap getIcon(Intent intent) {
+ return getIcon(intent, null);
+ }
+
+ public Bitmap getIcon(Intent intent, String title) {
synchronized (mCache) {
final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0);
ComponentName component = intent.getComponent();
- if (resolveInfo == null || component == null) {
+ if (component == null) {
return mDefaultIcon;
}
CacheEntry entry = cacheLocked(component, resolveInfo, null);
+ if (title != null) {
+ entry.title = title;
+ }
return entry.icon;
}
}
@@ -214,21 +252,35 @@ public class IconCache {
mCache.put(componentName, entry);
- ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
- if (labelCache != null && labelCache.containsKey(key)) {
- entry.title = labelCache.get(key).toString();
+ if (info != null) {
+ ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
+ if (labelCache != null && labelCache.containsKey(key)) {
+ entry.title = labelCache.get(key).toString();
+ } else {
+ entry.title = info.loadLabel(mPackageManager).toString();
+ if (labelCache != null) {
+ labelCache.put(key, entry.title);
+ }
+ }
+ if (entry.title == null) {
+ entry.title = info.activityInfo.name;
+ }
+
+ entry.icon = Utilities.createIconBitmap(
+ getFullResIcon(info), mContext);
} else {
- entry.title = info.loadLabel(mPackageManager).toString();
- if (labelCache != null) {
- labelCache.put(key, entry.title);
+ entry.title = "";
+ Bitmap preloaded = getPreloadedIcon(componentName);
+ if (preloaded != null) {
+ if (DEBUG) Log.d(TAG, "using preloaded icon for " +
+ componentName.toShortString());
+ entry.icon = preloaded;
+ } else {
+ if (DEBUG) Log.d(TAG, "using default icon for " +
+ componentName.toShortString());
+ entry.icon = mDefaultIcon;
}
}
- if (entry.title == null) {
- entry.title = info.activityInfo.name;
- }
-
- entry.icon = Utilities.createIconBitmap(
- getFullResIcon(info), mContext);
}
return entry;
}
@@ -243,4 +295,137 @@ public class IconCache {
return set;
}
}
+
+ /**
+ * Pre-load an icon into the persistent cache.
+ *
+ * Queries for a component that does not exist in the package manager
+ * will be answered by the persistent cache.
+ *
+ * @param context application context
+ * @param componentName the icon should be returned for this component
+ * @param icon the icon to be persisted
+ * @param dpi the native density of the icon
+ */
+ public static void preloadIcon(Context context, ComponentName componentName, Bitmap icon,
+ int dpi) {
+ // TODO rescale to the correct native DPI
+ try {
+ PackageManager packageManager = context.getPackageManager();
+ packageManager.getActivityIcon(componentName);
+ // component is present on the system already, do nothing
+ return;
+ } catch (PackageManager.NameNotFoundException e) {
+ // pass
+ }
+
+ final String key = componentName.flattenToString();
+ FileOutputStream resourceFile = null;
+ try {
+ resourceFile = context.openFileOutput(getResourceFilename(componentName),
+ Context.MODE_PRIVATE);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ if (icon.compress(android.graphics.Bitmap.CompressFormat.PNG, 75, os)) {
+ byte[] buffer = os.toByteArray();
+ resourceFile.write(buffer, 0, buffer.length);
+ } else {
+ Log.w(TAG, "failed to encode cache for " + key);
+ return;
+ }
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "failed to pre-load cache for " + key, e);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to pre-load cache for " + key, e);
+ } finally {
+ if (resourceFile != null) {
+ try {
+ resourceFile.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to save restored icon for: " + key, e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Read a pre-loaded icon from the persistent icon cache.
+ *
+ * @param componentName the component that should own the icon
+ * @returns a bitmap if one is cached, or null.
+ */
+ private Bitmap getPreloadedIcon(ComponentName componentName) {
+ final String key = componentName.flattenToShortString();
+
+ if (DEBUG) Log.v(TAG, "looking for pre-load icon for " + key);
+ Bitmap icon = null;
+ FileInputStream resourceFile = null;
+ try {
+ resourceFile = mContext.openFileInput(getResourceFilename(componentName));
+ byte[] buffer = new byte[1024];
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ int bytesRead = 0;
+ while(bytesRead >= 0) {
+ bytes.write(buffer, 0, bytesRead);
+ bytesRead = resourceFile.read(buffer, 0, buffer.length);
+ }
+ if (DEBUG) Log.d(TAG, "read " + bytes.size());
+ icon = BitmapFactory.decodeByteArray(bytes.toByteArray(), 0, bytes.size());
+ if (icon == null) {
+ Log.w(TAG, "failed to decode pre-load icon for " + key);
+ }
+ } catch (FileNotFoundException e) {
+ if (DEBUG) Log.d(TAG, "there is no restored icon for: " + key, e);
+ } catch (IOException e) {
+ Log.w(TAG, "failed to read pre-load icon for: " + key, e);
+ } finally {
+ if(resourceFile != null) {
+ try {
+ resourceFile.close();
+ } catch (IOException e) {
+ Log.d(TAG, "failed to manage pre-load icon file: " + key, e);
+ }
+ }
+ }
+
+ if (icon != null) {
+ // TODO: handle alpha mask in the view layer
+ Bitmap b = Bitmap.createBitmap(Math.max(icon.getWidth(), 1),
+ Math.max(icon.getHeight(), 1),
+ Bitmap.Config.ARGB_8888);
+ Canvas c = new Canvas(b);
+ Paint paint = new Paint();
+ paint.setAlpha(127);
+ c.drawBitmap(icon, 0, 0, paint);
+ c.setBitmap(null);
+ icon.recycle();
+ icon = b;
+ }
+
+ return icon;
+ }
+
+ /**
+ * Remove a pre-loaded icon from the persistent icon cache.
+ *
+ * @param componentName the component that should own the icon
+ * @returns true on success
+ */
+ public boolean deletePreloadedIcon(ComponentName componentName) {
+ if (componentName == null) {
+ return false;
+ }
+ if (mCache.remove(componentName) != null) {
+ if (DEBUG) Log.d(TAG, "removed pre-loaded icon from the in-memory cache");
+ }
+ boolean success = mContext.deleteFile(getResourceFilename(componentName));
+ if (DEBUG && success) Log.d(TAG, "removed pre-loaded icon from persistent cache");
+
+ return success;
+ }
+
+ private static String getResourceFilename(ComponentName component) {
+ String resourceName = component.flattenToShortString();
+ String filename = resourceName.replace(File.separatorChar, '_');
+ return RESOURCE_FILE_PREFIX + filename;
+ }
}
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index a45f2931be..c706cf1745 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -50,7 +50,9 @@ import android.util.Base64;
import android.util.Log;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -136,6 +138,8 @@ public class LauncherBackupHelper implements BackupHelper {
private static final int SCREEN_RANK_INDEX = 2;
+ private static IconCache mIconCache;
+
private final Context mContext;
private final boolean mRestoreEnabled;
@@ -441,14 +445,12 @@ public class LauncherBackupHelper implements BackupHelper {
private void backupIcons(Journal in, BackupDataOutput data, Journal out,
ArrayList keys) throws IOException {
// persist icons that haven't been persisted yet
- final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState == null) {
+ if (!initializeIconCache()) {
dataChanged(); // try again later
if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying icon backup");
return;
}
final ContentResolver cr = mContext.getContentResolver();
- final IconCache iconCache = appState.getIconCache();
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
// read the old ID set
@@ -487,9 +489,9 @@ public class LauncherBackupHelper implements BackupHelper {
if (DEBUG) Log.d(TAG, "I can count this high: " + out.rows);
if ((out.rows - startRows) < MAX_ICONS_PER_PASS) {
if (VERBOSE) Log.v(TAG, "saving icon " + backupKey);
- Bitmap icon = iconCache.getIcon(intent);
+ Bitmap icon = mIconCache.getIcon(intent);
keys.add(key);
- if (icon != null && !iconCache.isDefaultIcon(icon)) {
+ if (icon != null && !mIconCache.isDefaultIcon(icon)) {
byte[] blob = packIcon(dpi, icon);
writeRowToBackup(key, blob, out, data);
}
@@ -530,25 +532,33 @@ public class LauncherBackupHelper implements BackupHelper {
if (VERBOSE) Log.v(TAG, "unpacking icon " + key.id);
if (DEBUG) Log.d(TAG, "read (" + buffer.length + "): " +
Base64.encodeToString(buffer, 0, dataSize, Base64.NO_WRAP));
+
try {
Resource res = unpackIcon(buffer, 0, dataSize);
- if (DEBUG) Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
- if (DEBUG_PAYLOAD) Log.d(TAG, "read " +
- Base64.encodeToString(res.data, 0, res.data.length,
- Base64.NO_WRAP));
+ if (DEBUG) {
+ Log.d(TAG, "unpacked " + res.dpi + " dpi icon");
+ }
+ if (DEBUG_PAYLOAD) {
+ Log.d(TAG, "read " +
+ Base64.encodeToString(res.data, 0, res.data.length,
+ Base64.NO_WRAP));
+ }
Bitmap icon = BitmapFactory.decodeByteArray(res.data, 0, res.data.length);
if (icon == null) {
Log.w(TAG, "failed to unpack icon for " + key.name);
}
if (!mRestoreEnabled) {
- if (VERBOSE) Log.v(TAG, "restore not enabled: skipping database mutation");
+ if (VERBOSE) {
+ Log.v(TAG, "restore not enabled: skipping database mutation");
+ }
return;
} else {
- // future site of icon cache mutation
+ IconCache.preloadIcon(mContext, ComponentName.unflattenFromString(key.name),
+ icon, res.dpi);
}
- } catch (InvalidProtocolBufferNanoException e) {
- Log.e(TAG, "failed to decode icon", e);
+ } catch (IOException e) {
+ Log.d(TAG, "failed to save restored icon for: " + key.name, e);
}
}
@@ -566,15 +576,13 @@ public class LauncherBackupHelper implements BackupHelper {
ArrayList keys) throws IOException {
// persist static widget info that hasn't been persisted yet
final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
- if (appState == null) {
- dataChanged(); // try again later
- if (DEBUG) Log.d(TAG, "Launcher is not initialized, delaying widget backup");
+ if (appState == null || !initializeIconCache()) {
+ Log.w(TAG, "Failed to get icon cache during restore");
return;
}
final ContentResolver cr = mContext.getContentResolver();
final WidgetPreviewLoader previewLoader = new WidgetPreviewLoader(mContext);
final PagedViewCellLayout widgetSpacingLayout = new PagedViewCellLayout(mContext);
- final IconCache iconCache = appState.getIconCache();
final int dpi = mContext.getResources().getDisplayMetrics().densityDpi;
final DeviceProfile profile = appState.getDynamicGrid().getDeviceProfile();
if (DEBUG) Log.d(TAG, "cellWidthPx: " + profile.cellWidthPx);
@@ -617,7 +625,7 @@ public class LauncherBackupHelper implements BackupHelper {
if (VERBOSE) Log.v(TAG, "saving widget " + backupKey);
previewLoader.setPreviewSize(spanX * profile.cellWidthPx,
spanY * profile.cellHeightPx, widgetSpacingLayout);
- byte[] blob = packWidget(dpi, previewLoader, iconCache, provider);
+ byte[] blob = packWidget(dpi, previewLoader, mIconCache, provider);
keys.add(key);
writeRowToBackup(key, blob, out, data);
@@ -882,7 +890,7 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Deserialize an icon resource from persistence, after verifying checksum wrapper. */
- private Resource unpackIcon(byte[] buffer, int offset, int dataSize)
+ private static Resource unpackIcon(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
Resource res = new Resource();
MessageNano.mergeFrom(res, readCheckedBytes(buffer, offset, dataSize));
@@ -1080,7 +1088,7 @@ public class LauncherBackupHelper implements BackupHelper {
}
/** Unwrap a proto message from a CheckedMessage, verifying the checksum. */
- private byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
+ private static byte[] readCheckedBytes(byte[] buffer, int offset, int dataSize)
throws InvalidProtocolBufferNanoException {
CheckedMessage wrapper = new CheckedMessage();
MessageNano.mergeFrom(wrapper, buffer, offset, dataSize);
@@ -1104,6 +1112,23 @@ public class LauncherBackupHelper implements BackupHelper {
return mWidgetMap.get(component);
}
+
+ private boolean initializeIconCache() {
+ if (mIconCache != null) {
+ return true;
+ }
+
+ final LauncherAppState appState = LauncherAppState.getInstanceNoCreate();
+ if (appState == null) {
+ Throwable stackTrace = new Throwable();
+ stackTrace.fillInStackTrace();
+ Log.w(TAG, "Failed to get app state during backup/restore", stackTrace);
+ return false;
+ }
+ mIconCache = appState.getIconCache();
+ return mIconCache != null;
+ }
+
private class KeyParsingException extends Throwable {
private KeyParsingException(Throwable cause) {
super(cause);
diff --git a/src/com/android/launcher3/LauncherClings.java b/src/com/android/launcher3/LauncherClings.java
index 00711fe4b2..894f64da4f 100644
--- a/src/com/android/launcher3/LauncherClings.java
+++ b/src/com/android/launcher3/LauncherClings.java
@@ -41,7 +41,6 @@ class LauncherClings {
private static final String FOLDER_CLING_DISMISSED_KEY = "cling_gel.folder.dismissed";
private static final boolean DISABLE_CLINGS = false;
- private static final boolean DISABLE_CUSTOM_CLINGS = true;
private static final int SHOW_CLING_DURATION = 250;
private static final int DISMISS_CLING_DURATION = 200;
@@ -196,8 +195,7 @@ class LauncherClings {
SharedPreferences sharedPrefs = mLauncher.getSharedPrefs();
return areClingsEnabled() &&
!sharedPrefs.getBoolean(FIRST_RUN_CLING_DISMISSED_KEY, false) &&
- !sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false) &&
- LauncherAppState.getLauncherProvider().wasNewDbCreated();
+ !sharedPrefs.getBoolean(MIGRATION_CLING_DISMISSED_KEY, false);
}
public void removeFirstRunAndMigrationClings() {
@@ -213,24 +211,6 @@ class LauncherClings {
*/
public void showFirstRunCling() {
if (!skipCustomClingIfNoAccounts()) {
- SharedPreferences sharedPrefs = mLauncher.getSharedPrefs();
- // If we're not using the default workspace layout, replace workspace cling
- // with a custom workspace cling (usually specified in an overlay)
- // For now, only do this on tablets
- if (!DISABLE_CUSTOM_CLINGS) {
- if (sharedPrefs.getInt(LauncherProvider.DEFAULT_WORKSPACE_RESOURCE_ID, 0) != 0 &&
- mLauncher.getResources().getBoolean(R.bool.config_useCustomClings)) {
- // Use a custom cling
- View cling = mLauncher.findViewById(R.id.workspace_cling);
- ViewGroup clingParent = (ViewGroup) cling.getParent();
- int clingIndex = clingParent.indexOfChild(cling);
- clingParent.removeViewAt(clingIndex);
- View customCling = mInflater.inflate(R.layout.custom_workspace_cling,
- clingParent, false);
- clingParent.addView(customCling, clingIndex);
- customCling.setId(R.id.workspace_cling);
- }
- }
Cling cling = (Cling) mLauncher.findViewById(R.id.first_run_cling);
if (cling != null) {
String sbHintStr = mLauncher.getFirstRunClingSearchBarHint();
@@ -292,6 +272,7 @@ class LauncherClings {
removeCling(R.id.workspace_cling);
}
}
+
public Cling showFoldersCling() {
SharedPreferences sharedPrefs = mLauncher.getSharedPrefs();
// Enable the clings only if they have not been dismissed before
@@ -314,6 +295,14 @@ class LauncherClings {
}
}
+ public static void synchonouslyMarkFirstRunClingDismissed(Context ctx) {
+ SharedPreferences prefs = ctx.getSharedPreferences(
+ LauncherAppState.getSharedPreferencesKey(),Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(LauncherClings.FIRST_RUN_CLING_DISMISSED_KEY, true);
+ editor.commit();
+ }
+
/** Removes the cling outright from the DragLayer */
private void removeCling(int id) {
final View cling = mLauncher.findViewById(id);
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index f4fa10ffac..007fd7a4af 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -318,10 +318,16 @@ public class LauncherModel extends BroadcastReceiver {
public void run() {
Callbacks cb = mCallbacks != null ? mCallbacks.get() : null;
if (callbacks == cb && cb != null) {
- callbacks.bindAppsAdded(null, null, null, allAppsApps);
if (!restoredAppsFinal.isEmpty()) {
+ for (AppInfo info : restoredAppsFinal) {
+ final Intent intent = info.getIntent();
+ if (intent != null) {
+ mIconCache.deletePreloadedIcon(intent.getComponent());
+ }
+ }
callbacks.bindAppsUpdated(restoredAppsFinal);
}
+ callbacks.bindAppsAdded(null, null, null, allAppsApps);
}
}
});
@@ -2667,6 +2673,7 @@ public class LauncherModel extends BroadcastReceiver {
case OP_ADD:
for (int i=0; i