Merge changes from topic "am-b7c4122a-a8cc-4b3b-b83c-0c9eba63d2ad" into ub-launcher3-master
* changes:
[automerger] Adjusting opacity values for all apps. am: f30c63c593
Adjusting opacity values for all apps.
diff --git a/go/res/values-v26/bools.xml b/go/res/values-v26/bools.xml
new file mode 100644
index 0000000..cc4a7ba
--- /dev/null
+++ b/go/res/values-v26/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* Copyright 2017, 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.
+*/
+-->
+
+<resources>
+ <bool name="notification_badging_enabled">false</bool>
+</resources>
\ No newline at end of file
diff --git a/go/res/values/override.xml b/go/res/values/override.xml
new file mode 100644
index 0000000..268cb98
--- /dev/null
+++ b/go/res/values/override.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2017 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.
+*/
+-->
+<resources>
+ <!-- String representing the intent to delete a package. -->
+ <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;B.android.intent.extra.RETURN_RESULT=true;end</string>
+</resources>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index a62947e..b41172b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -19,6 +19,9 @@
q=<query> to the data to the intent -->
<string name="market_search_intent" translatable="false">market://search?c=apps</string>
+ <!-- String representing the intent to delete a package.-->
+ <string name="delete_package_intent" translatable="false">#Intent;action=android.intent.action.DELETE;launchFlags=0x10800000;end</string>
+
<!-- Values for icon shape overrides. These should correspond to entries defined
in icon_shape_override_paths_names -->
<string-array translatable="false" name="icon_shape_override_paths_values">
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 1a405f9..a486a3a 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -55,7 +55,6 @@
private final int[] mDirectionVector = new int[2];
private final int[] mLastDirectionVector = new int[2];
- private final int[] mTmpPt = new int[2];
private final IntRange mTempRange1 = new IntRange();
private final IntRange mTempRange2 = new IntRange();
@@ -344,13 +343,12 @@
return rect;
}
- /**
- * This is the final step of the resize. Here we save the new widget size and position
- * to LauncherModel and animate the resize frame.
- */
- public void commitResize() {
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ // We are done with resizing the widget. Save the widget size & position to LauncherModel
resizeWidgetIfNeeded(true);
- requestLayout();
}
private void onTouchUp() {
diff --git a/src/com/android/launcher3/SettingsActivity.java b/src/com/android/launcher3/SettingsActivity.java
index 9046372..5bdc1f5 100644
--- a/src/com/android/launcher3/SettingsActivity.java
+++ b/src/com/android/launcher3/SettingsActivity.java
@@ -98,6 +98,8 @@
getPreferenceScreen().removePreference(
findPreference(SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY));
getPreferenceScreen().removePreference(iconBadgingPref);
+ } else if (!getResources().getBoolean(R.bool.notification_badging_enabled)) {
+ getPreferenceScreen().removePreference(iconBadgingPref);
} else {
// Listen to system notification badge settings while this UI is active.
mIconBadgingObserver = new IconBadgingObserver(
diff --git a/src/com/android/launcher3/UninstallDropTarget.java b/src/com/android/launcher3/UninstallDropTarget.java
index e15cf9f..84d6a9b 100644
--- a/src/com/android/launcher3/UninstallDropTarget.java
+++ b/src/com/android/launcher3/UninstallDropTarget.java
@@ -11,12 +11,17 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.util.AttributeSet;
+import android.util.Log;
import android.widget.Toast;
import com.android.launcher3.compat.LauncherAppsCompat;
+import java.net.URISyntaxException;
+
public class UninstallDropTarget extends ButtonDropTarget {
+ private static final String TAG = "UninstallDropTarget";
+
public UninstallDropTarget(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -99,25 +104,28 @@
final Launcher launcher, ItemInfo info, DropTargetResultCallback callback) {
final ComponentName cn = getUninstallTarget(launcher, info);
- final boolean isUninstallable;
+ boolean canUninstall;
if (cn == null) {
// System applications cannot be installed. For now, show a toast explaining that.
// We may give them the option of disabling apps this way.
Toast.makeText(launcher, R.string.uninstall_system_app_text, Toast.LENGTH_SHORT).show();
- isUninstallable = false;
+ canUninstall = false;
} else {
- Intent intent = new Intent(Intent.ACTION_DELETE,
- Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
- intent.putExtra(Intent.EXTRA_USER, info.user);
- launcher.startActivity(intent);
- isUninstallable = true;
+ try {
+ Intent i = Intent.parseUri(launcher.getString(R.string.delete_package_intent), 0)
+ .setData(Uri.fromParts("package", cn.getPackageName(), cn.getClassName()))
+ .putExtra(Intent.EXTRA_USER, info.user);
+ launcher.startActivity(i);
+ canUninstall = true;
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Failed to parse intent to start uninstall activity for item=" + info);
+ canUninstall = false;
+ }
}
if (callback != null) {
- sendUninstallResult(launcher, isUninstallable, cn, info.user, callback);
+ sendUninstallResult(launcher, canUninstall, cn, info.user, callback);
}
- return isUninstallable;
+ return canUninstall;
}
/**
diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java
index 6233fab..fe5ff2a 100644
--- a/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java
+++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVOMR1.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
+import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.Log;
@@ -48,8 +49,7 @@
mOCLClass = Class.forName("android.app.WallpaperManager$OnColorsChangedListener");
mAddOCLMethod = WallpaperManager.class.getDeclaredMethod(
- "addOnColorsChangedListener", mOCLClass);
-
+ "addOnColorsChangedListener", mOCLClass, Handler.class);
mWCGetMethod = WallpaperManager.class.getDeclaredMethod("getWallpaperColors", int.class);
Class wallpaperColorsClass = mWCGetMethod.getReturnType();
mWCGetPrimaryColorMethod = wallpaperColorsClass.getDeclaredMethod("getPrimaryColor");
@@ -89,7 +89,7 @@
}
});
try {
- mAddOCLMethod.invoke(mWm, onChangeListener);
+ mAddOCLMethod.invoke(mWm, onChangeListener, null);
} catch (Exception e) {
Log.e(TAG, "Error calling wallpaper API", e);
}
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index c007859..c88359b 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -54,8 +54,6 @@
public static final boolean NO_ALL_APPS_ICON = true;
// When enabled the status bar may show dark icons based on the top of the wallpaper.
public static final boolean LIGHT_STATUS_BAR = false;
- // When enabled icons are badged with the number of notifications associated with that app.
- public static final boolean BADGE_ICONS = true;
// When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
public static final boolean LEGACY_ICON_TREATMENT = true;
// When enabled, adaptive icons would have shadows baked when being stored to icon cache.
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index ee6a0e0..fde7995 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -544,7 +544,6 @@
public void clearResizeFrame() {
if (mCurrentResizeFrame != null) {
- mCurrentResizeFrame.commitResize();
removeView(mCurrentResizeFrame);
mCurrentResizeFrame = null;
}
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 6a70989..9126626 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -32,7 +32,6 @@
import android.util.Pair;
import com.android.launcher3.LauncherModel;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.SettingsObserver;
@@ -164,9 +163,6 @@
}
public static void setNotificationsChangedListener(NotificationsChangedListener listener) {
- if (!FeatureFlags.BADGE_ICONS) {
- return;
- }
sNotificationsChangedListener = listener;
NotificationListener notificationListener = getInstanceIfConnected();
diff --git a/src/com/android/launcher3/widget/WidgetListRowEntry.java b/src/com/android/launcher3/widget/WidgetListRowEntry.java
index 3e89eeb..335b8c7 100644
--- a/src/com/android/launcher3/widget/WidgetListRowEntry.java
+++ b/src/com/android/launcher3/widget/WidgetListRowEntry.java
@@ -41,4 +41,8 @@
this.widgets = items;
}
+ @Override
+ public String toString() {
+ return pkgItem.packageName + ":" + widgets.size();
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetsContainerView.java b/src/com/android/launcher3/widget/WidgetsContainerView.java
index 14a9d17..acec3dd 100644
--- a/src/com/android/launcher3/widget/WidgetsContainerView.java
+++ b/src/com/android/launcher3/widget/WidgetsContainerView.java
@@ -17,10 +17,12 @@
package com.android.launcher3.widget;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.graphics.Point;
import android.support.v7.widget.LinearLayoutManager;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
@@ -31,8 +33,10 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.PackageItemInfo;
@@ -74,7 +78,11 @@
public WidgetsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = Launcher.getLauncher(context);
- mAdapter = new WidgetsListAdapter(this, this, context);
+ LauncherAppState apps = LauncherAppState.getInstance(context);
+ mAdapter = new WidgetsListAdapter(context, LayoutInflater.from(context),
+ apps.getWidgetCache(), new AlphabeticIndexCompat(context), this, this,
+ new WidgetsDiffReporter(apps.getIconCache()));
+ mAdapter.setNotifyListener();
if (LOGD) {
Log.d(TAG, "WidgetsContainerView constructor");
}
@@ -232,7 +240,6 @@
*/
public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> model) {
mAdapter.setWidgets(model);
- mAdapter.notifyDataSetChanged();
View loader = getContentView().findViewById(R.id.loader);
if (loader != null) {
diff --git a/src/com/android/launcher3/widget/WidgetsDiffReporter.java b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
new file mode 100644
index 0000000..d9c9ef9
--- /dev/null
+++ b/src/com/android/launcher3/widget/WidgetsDiffReporter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.util.Log;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.widget.WidgetsListAdapter.WidgetListRowEntryComparator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Do diff on widget's tray list items and call the {@link NotifyListener} methods accordingly.
+ */
+public class WidgetsDiffReporter {
+ private final boolean DEBUG = true;
+ private final String TAG = "WidgetsDiffReporter";
+ private final IconCache mIconCache;
+ private NotifyListener mListener;
+
+ public interface NotifyListener {
+ void notifyDataSetChanged();
+ void notifyItemChanged(int index);
+ void notifyItemInserted(int index);
+ void notifyItemRemoved(int index);
+ }
+
+ public WidgetsDiffReporter(IconCache iconCache) {
+ mIconCache = iconCache;
+ }
+
+ public void setListener(NotifyListener listener) {
+ mListener = listener;
+ }
+
+ public void process(ArrayList<WidgetListRowEntry> currentEntries,
+ ArrayList<WidgetListRowEntry> newEntries, WidgetListRowEntryComparator comparator) {
+ if (DEBUG) {
+ Log.d(TAG, "process oldEntries#=" + currentEntries.size()
+ + " newEntries#=" + newEntries.size());
+ }
+ if (currentEntries.size() == 0 && newEntries.size() > 0) {
+ currentEntries.addAll(newEntries);
+ mListener.notifyDataSetChanged();
+ return;
+ }
+ ArrayList<WidgetListRowEntry> orgEntries =
+ (ArrayList<WidgetListRowEntry>) currentEntries.clone();
+ Iterator<WidgetListRowEntry> orgIter = orgEntries.iterator();
+ Iterator<WidgetListRowEntry> newIter = newEntries.iterator();
+
+ WidgetListRowEntry orgRowEntry = orgIter.next();
+ WidgetListRowEntry newRowEntry = newIter.next();
+
+ do {
+ int diff = comparePackageName(orgRowEntry, newRowEntry, comparator);
+ if (DEBUG) {
+ Log.d(TAG, String.format("diff=%d orgRowEntry (%s) newRowEntry (%s)",
+ diff, orgRowEntry != null? orgRowEntry.toString() : null,
+ newRowEntry != null? newRowEntry.toString() : null));
+ }
+ int index = -1;
+ if (diff < 0) {
+ index = currentEntries.indexOf(orgRowEntry);
+ mListener.notifyItemRemoved(index);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemRemoved called (%d)%s", index,
+ orgRowEntry.titleSectionName));
+ }
+ currentEntries.remove(index);
+ orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+ } else if (diff > 0) {
+ index = orgRowEntry != null? currentEntries.indexOf(orgRowEntry):
+ currentEntries.size();
+ currentEntries.add(index, newRowEntry);
+ newRowEntry = newIter.hasNext() ? newIter.next() : null;
+ mListener.notifyItemInserted(index);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemInserted called (%d)%s", index,
+ newRowEntry.titleSectionName));
+ }
+ } else {
+ // same package name but,
+ // did the icon, title, etc, change?
+ // or did the widget size and desc, span, etc change?
+ if (!isSamePackageItemInfo(orgRowEntry.pkgItem, newRowEntry.pkgItem) ||
+ !orgRowEntry.widgets.equals(newRowEntry.widgets)) {
+ index = currentEntries.indexOf(orgRowEntry);
+ currentEntries.set(index, newRowEntry);
+ mListener.notifyItemChanged(index);
+ if (DEBUG) {
+ Log.d(TAG, String.format("notifyItemChanged called (%d)%s", index,
+ newRowEntry.titleSectionName));
+ }
+ }
+ orgRowEntry = orgIter.hasNext() ? orgIter.next() : null;
+ newRowEntry = newIter.hasNext() ? newIter.next() : null;
+ }
+ } while(orgRowEntry != null || newRowEntry != null);
+ }
+
+ /**
+ * Compare package name using the same comparator as in {@link WidgetsListAdapter}.
+ * Also handle null row pointers.
+ */
+ private int comparePackageName(WidgetListRowEntry curRow, WidgetListRowEntry newRow,
+ WidgetListRowEntryComparator comparator) {
+ if (curRow == null && newRow == null) {
+ throw new IllegalStateException("Cannot compare PackageItemInfo if both rows are null.");
+ }
+
+ if (curRow == null && newRow != null) {
+ return 1; // new row needs to be inserted
+ } else if (curRow != null && newRow == null) {
+ return -1; // old row needs to be deleted
+ }
+ return comparator.compare(curRow, newRow);
+ }
+
+ private boolean isSamePackageItemInfo(PackageItemInfo curInfo, PackageItemInfo newInfo) {
+ return curInfo.iconBitmap.equals(newInfo.iconBitmap) &&
+ !mIconCache.isDefaultIcon(curInfo.iconBitmap, curInfo.user);
+ }
+}
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a1eb0ab..6b1800c 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -21,9 +21,10 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.WidgetPreviewLoader;
import com.android.launcher3.compat.AlphabeticIndexCompat;
@@ -55,40 +56,67 @@
private final WidgetPreviewLoader mWidgetPreviewLoader;
private final LayoutInflater mLayoutInflater;
-
- private final View.OnClickListener mIconClickListener;
- private final View.OnLongClickListener mIconLongClickListener;
-
- private final ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
private final AlphabeticIndexCompat mIndexer;
+ private final OnClickListener mIconClickListener;
+ private final OnLongClickListener mIconLongClickListener;
private final int mIndent;
+ private ArrayList<WidgetListRowEntry> mEntries = new ArrayList<>();
+ private final WidgetsDiffReporter mDiffReporter;
- public WidgetsListAdapter(View.OnClickListener iconClickListener,
- View.OnLongClickListener iconLongClickListener,
- Context context) {
- mLayoutInflater = LayoutInflater.from(context);
- mWidgetPreviewLoader = LauncherAppState.getInstance(context).getWidgetCache();
-
- mIndexer = new AlphabeticIndexCompat(context);
-
+ public WidgetsListAdapter(Context context, LayoutInflater layoutInflater,
+ WidgetPreviewLoader widgetPreviewLoader, AlphabeticIndexCompat indexCompat,
+ OnClickListener iconClickListener, OnLongClickListener iconLongClickListener,
+ WidgetsDiffReporter diffReporter) {
+ mLayoutInflater = layoutInflater;
+ mWidgetPreviewLoader = widgetPreviewLoader;
+ mIndexer = indexCompat;
mIconClickListener = iconClickListener;
mIconLongClickListener = iconLongClickListener;
mIndent = context.getResources().getDimensionPixelSize(R.dimen.widget_section_indent);
+ mDiffReporter = diffReporter;
}
- public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
- mEntries.clear();
- WidgetItemComparator widgetComparator = new WidgetItemComparator();
+ public void setNotifyListener() {
+ mDiffReporter.setListener(new WidgetsDiffReporter.NotifyListener() {
+ @Override
+ public void notifyDataSetChanged() {
+ WidgetsListAdapter.this.notifyDataSetChanged();
+ }
+ @Override
+ public void notifyItemChanged(int index) {
+ WidgetsListAdapter.this.notifyItemChanged(index);
+ }
+
+ @Override
+ public void notifyItemInserted(int index) {
+ WidgetsListAdapter.this.notifyItemInserted(index);
+ }
+
+ @Override
+ public void notifyItemRemoved(int index) {
+ WidgetsListAdapter.this.notifyItemRemoved(index);
+ }
+ });
+ }
+
+ /**
+ * Update the widget list.
+ */
+ public void setWidgets(MultiHashMap<PackageItemInfo, WidgetItem> widgets) {
+ ArrayList<WidgetListRowEntry> tempEntries = new ArrayList<>();
+
+ WidgetItemComparator widgetComparator = new WidgetItemComparator();
for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : widgets.entrySet()) {
WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
row.titleSectionName = mIndexer.computeSectionName(row.pkgItem.title);
Collections.sort(row.widgets, widgetComparator);
- mEntries.add(row);
+ tempEntries.add(row);
}
-
- Collections.sort(mEntries, new WidgetListRowEntryComparator());
+ WidgetListRowEntryComparator rowComparator = new WidgetListRowEntryComparator();
+ Collections.sort(tempEntries, rowComparator);
+ mDiffReporter.process(mEntries, tempEntries, rowComparator);
}
@Override
diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
new file mode 100644
index 0000000..40b65e4
--- /dev/null
+++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 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.widget;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.compat.AlphabeticIndexCompat;
+import com.android.launcher3.compat.AppWidgetManagerCompat;
+import com.android.launcher3.model.PackageItemInfo;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.util.MultiHashMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class WidgetsListAdapterTest {
+
+ private final String TAG = "WidgetsListAdapterTest";
+
+ @Mock private LayoutInflater mMockLayoutInflater;
+ @Mock private WidgetPreviewLoader mMockWidgetCache;
+ @Mock private WidgetsDiffReporter.NotifyListener mListener;
+ @Mock private IconCache mIconCache;
+
+ private WidgetsListAdapter mAdapter;
+ private AlphabeticIndexCompat mIndexCompat;
+ private InvariantDeviceProfile mTestProfile;
+ private Context mContext;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+ mTestProfile = new InvariantDeviceProfile();
+ mTestProfile.numRows = 5;
+ mTestProfile.numColumns = 5;
+ mIndexCompat = new AlphabeticIndexCompat(mContext);
+ WidgetsDiffReporter reporter = new WidgetsDiffReporter(mIconCache);
+ reporter.setListener(mListener);
+ mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache,
+ mIndexCompat, null, null, reporter);
+ }
+
+ @Test
+ public void test_notifyDataSetChanged() throws Exception {
+ mAdapter.setWidgets(generateSampleMap(1));
+ verify(mListener, times(1)).notifyDataSetChanged();
+ }
+
+ @Test
+ public void test_notifyItemInserted() throws Exception {
+ mAdapter.setWidgets(generateSampleMap(1));
+ mAdapter.setWidgets(generateSampleMap(2));
+ verify(mListener, times(1)).notifyDataSetChanged();
+ verify(mListener, times(1)).notifyItemInserted(1);
+ }
+
+ @Test
+ public void test_notifyItemRemoved() throws Exception {
+ mAdapter.setWidgets(generateSampleMap(2));
+ mAdapter.setWidgets(generateSampleMap(1));
+ verify(mListener, times(1)).notifyDataSetChanged();
+ verify(mListener, times(1)).notifyItemRemoved(1);
+ }
+
+ @Test
+ public void testNotifyItemChanged_PackageIconDiff() throws Exception {
+ mAdapter.setWidgets(generateSampleMap(1));
+ mAdapter.setWidgets(generateSampleMap(1));
+ verify(mListener, times(1)).notifyDataSetChanged();
+ verify(mListener, times(1)).notifyItemChanged(0);
+ }
+
+ @Test
+ public void testNotifyItemChanged_widgetItemInfoDiff() throws Exception {
+ // TODO: same package name but item number changed
+ }
+
+ @Test
+ public void testNotifyItemInsertedRemoved_hodgepodge() throws Exception {
+ // TODO: insert and remove combined. curMap
+ // newMap [A, C, D] [A, B, E]
+ // B - C < 0, removed B from index 1 [A, E]
+ // E - C > 0, C inserted to index 1 [A, C, E]
+ // E - D > 0, D inserted to index 2 [A, C, D, E]
+ // E - null = -1, E deleted from index 3 [A, C, D]
+ }
+
+ /**
+ * Helper method to generate the sample widget model map that can be used for the tests
+ * @param num the number of WidgetItem the map should contain
+ * @return
+ */
+ private MultiHashMap<PackageItemInfo, WidgetItem> generateSampleMap(int num) {
+ MultiHashMap<PackageItemInfo, WidgetItem> newMap = new MultiHashMap();
+ if (num <= 0) return newMap;
+
+ PackageManager pm = mContext.getPackageManager();
+ AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext);
+ for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) {
+ WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo
+ .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile);
+
+ PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName());
+ pInfo.title = pInfo.packageName;
+ pInfo.user = wi.user;
+ pInfo.iconBitmap = Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8);
+ newMap.addToList(pInfo, wi);
+ if (newMap.size() == num) {
+ break;
+ }
+ }
+ return newMap;
+ }
+}