diff options
59 files changed, 1387 insertions, 461 deletions
diff --git a/Android.mk b/Android.mk index 713d0828a..6cb40c51a 100644 --- a/Android.mk +++ b/Android.mk @@ -31,6 +31,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ LOCAL_SRC_FILES := \ $(call all-java-files-under, src) \ $(call all-java-files-under, src_config) \ + $(call all-java-files-under, src_flags) \ $(call all-proto-files-under, protos) LOCAL_RESOURCE_DIR := \ diff --git a/build.gradle b/build.gradle index 51ac5a1a8..d6dbbabe6 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ android { sourceSets { main { res.srcDirs = ['res'] - java.srcDirs = ['src', 'src_config'] + java.srcDirs = ['src', 'src_flags'] manifest.srcFile 'AndroidManifest-common.xml' proto.srcDirs 'protos/' } @@ -90,6 +90,7 @@ protobuf { remove java javanano { option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano" + option "java_package=launcher_dump.proto|com.android.launcher3.model.nano" option "enum_style=java" } } diff --git a/protos/launcher_dump.proto b/protos/launcher_dump.proto new file mode 100644 index 000000000..dc8fbda25 --- /dev/null +++ b/protos/launcher_dump.proto @@ -0,0 +1,75 @@ +/* + * 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. + */ +syntax = "proto2"; + +option java_package = "com.android.launcher3.model"; +option java_outer_classname = "LauncherDumpProto"; + +package model; + +message DumpTarget { + enum Type { + NONE = 0; + ITEM = 1; + CONTAINER = 2; + } + + optional Type type = 1; + optional int32 page_id = 2; + optional int32 grid_x = 3; + optional int32 grid_y = 4; + + // For container types only + optional ContainerType container_type = 5; + + // For item types only + optional ItemType item_type = 6; + + optional string package_name = 7; // All ItemTypes except UNKNOWN type + optional string component = 8; // All ItemTypes except UNKNOWN type + optional string item_id = 9; // For Pinned Shortcuts and appWidgetId + + optional int32 span_x = 10 [default = 1];// Used for ItemType.WIDGET + optional int32 span_y = 11 [default = 1];// Used for ItemType.WIDGET + optional UserType user_type = 12; +} + +// Used to define what type of item a Target would represent. +enum ItemType { + UNKNOWN_ITEMTYPE = 0; // Launcher specific items + APP_ICON = 1; // Regular app icons + WIDGET = 2; // Elements from AppWidgetManager + SHORTCUT = 3; // ShortcutManager +} + +// Used to define what type of container a Target would represent. +enum ContainerType { + UNKNOWN_CONTAINERTYPE = 0; + WORKSPACE = 1; + HOTSEAT = 2; + FOLDER = 3; +} + +// Used to define what type of control a Target would represent. +enum UserType { + DEFAULT = 0; + WORK = 1; +} + +// Main message; +message LauncherImpression { + repeated DumpTarget targets = 1; +} diff --git a/res/interpolator/folder_closing_interpolator.xml b/res/interpolator/folder_closing_interpolator.xml new file mode 100644 index 000000000..e6e012eda --- /dev/null +++ b/res/interpolator/folder_closing_interpolator.xml @@ -0,0 +1,24 @@ +<?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. +*/ +--> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.8" + android:controlY1="0" + android:controlX2="0.8" + android:controlY2="1"/> diff --git a/res/interpolator/folder_opening_interpolator.xml b/res/interpolator/folder_opening_interpolator.xml new file mode 100644 index 000000000..b95d4548f --- /dev/null +++ b/res/interpolator/folder_opening_interpolator.xml @@ -0,0 +1,24 @@ +<?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. +*/ +--> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0.2" + android:controlY1="0" + android:controlX2="0" + android:controlY2="1"/> diff --git a/res/interpolator/folder_preview_item_closing_interpolator.xml b/res/interpolator/folder_preview_item_closing_interpolator.xml new file mode 100644 index 000000000..1d77081e5 --- /dev/null +++ b/res/interpolator/folder_preview_item_closing_interpolator.xml @@ -0,0 +1,24 @@ +<?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. +*/ +--> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="1" + android:controlY1="0" + android:controlX2="1" + android:controlY2="0"/> diff --git a/res/interpolator/folder_preview_item_opening_interpolator.xml b/res/interpolator/folder_preview_item_opening_interpolator.xml new file mode 100644 index 000000000..dcf01579b --- /dev/null +++ b/res/interpolator/folder_preview_item_opening_interpolator.xml @@ -0,0 +1,24 @@ +<?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. +*/ +--> + +<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android" + android:controlX1="0" + android:controlY1="1" + android:controlX2="0" + android:controlY2="1"/> diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml index 8d166536c..610dc1020 100644 --- a/res/values-b+sr+Latn/strings.xml +++ b/res/values-b+sr+Latn/strings.xml @@ -33,10 +33,8 @@ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite i zadržite da biste izabrali vidžet ili koristite prilagođene radnje."</string> <string name="widget_dims_format" msgid="2370757736025621599">"%1$d×%2$d"</string> <string name="widget_accessible_dims_format" msgid="3640149169885301790">"širina od %1$d i visina od %2$d"</string> - <!-- no translation found for add_item_request_drag_hint (8662194377800507270) --> - <skip /> - <!-- no translation found for place_automatically (1502491650329146581) --> - <skip /> + <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Dodirnite i zadržite da biste postavili na početni ekran"</string> + <string name="place_automatically" msgid="1502491650329146581">"Postavi automatski"</string> <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretražite aplikacije"</string> <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju..."</string> <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string> diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml index f1b3aeea4..4487dbf29 100644 --- a/res/values-be-rBY/strings.xml +++ b/res/values-be-rBY/strings.xml @@ -33,10 +33,8 @@ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Дакраніцеся двойчы і ўтрымлівайце, каб выбраць віджэт або выкарыстоўваць карыстальніцкія дзеянні."</string> <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string> <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Шырына: %1$d, вышыня: %2$d"</string> - <!-- no translation found for add_item_request_drag_hint (8662194377800507270) --> - <skip /> - <!-- no translation found for place_automatically (1502491650329146581) --> - <skip /> + <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Дакраніцеся і ўтрымлівайце, каб размясціць на галоўным экране"</string> + <string name="place_automatically" msgid="1502491650329146581">"Размясціць аўтаматычна"</string> <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Пошук у Праграмах"</string> <string name="all_apps_loading_message" msgid="7557140873644765180">"Ідзе загрузка праграм…"</string> <string name="all_apps_no_search_results" msgid="6332185285860416787">"Праграм, якія адпавядаюць запыту \"<xliff:g id="QUERY">%1$s</xliff:g>\", не знойдзена"</string> diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml index 4a658d2a1..28d349954 100644 --- a/res/values-bs-rBA/strings.xml +++ b/res/values-bs-rBA/strings.xml @@ -33,10 +33,8 @@ <string name="long_accessible_way_to_add" msgid="4289502106628154155">"Dvaput dodirnite & i držite da biste uzeli vidžet ili koristite prilagođene radnje."</string> <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string> <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string> - <!-- no translation found for add_item_request_drag_hint (8662194377800507270) --> - <skip /> - <!-- no translation found for place_automatically (1502491650329146581) --> - <skip /> + <string name="add_item_request_drag_hint" msgid="8662194377800507270">"Dodirnite i držite da postavite na početni ekran"</string> + <string name="place_automatically" msgid="1502491650329146581">"Postavi automatski"</string> <string name="all_apps_search_bar_hint" msgid="7084713969757597256">"Pretraži aplikacije"</string> <string name="all_apps_loading_message" msgid="7557140873644765180">"Aplikacije se učitavaju…"</string> <string name="all_apps_no_search_results" msgid="6332185285860416787">"Nije pronađena nijedna aplikacija koja odgovara upitu \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string> diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index bad70183a..f9a6742d8 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -40,6 +40,7 @@ import com.android.launcher3.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.badge.BadgeInfo; import com.android.launcher3.badge.BadgeRenderer; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.graphics.DrawableFactory; import com.android.launcher3.graphics.HolographicOutlineHelper; import com.android.launcher3.graphics.PreloadIconDrawable; @@ -547,7 +548,9 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { applyFromApplicationInfo((AppInfo) info); } else if (info instanceof ShortcutInfo) { applyFromShortcutInfo((ShortcutInfo) info); - if ((info.rank < FolderIcon.NUM_ITEMS_IN_PREVIEW) && (info.container >= 0)) { + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); + if (verifier.isItemInPreview(info.rank) && (info.container >= 0)) { View folderIcon = mLauncher.getWorkspace().getHomescreenIconByItemId(info.container); if (folderIcon != null) { @@ -587,6 +590,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver { .isEmpty(); } + public int getIconSize() { + return mIconSize; + } + /** * Interface to be implemented by the grand parent to allow click shadow effect. */ diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 8179dad29..70137aa7c 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -51,7 +51,7 @@ import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate; import com.android.launcher3.accessibility.FolderAccessibilityHelper; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; import com.android.launcher3.anim.PropertyListBuilder; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.graphics.DragPreviewProvider; import com.android.launcher3.util.CellAndSpan; @@ -568,7 +568,7 @@ public class CellLayout extends ViewGroup implements BubbleTextShadowHandler { try { dispatchRestoreInstanceState(states); } catch (IllegalArgumentException ex) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw ex; } // Mismatched viewId / viewType preventing restore. Skip restore on production builds. diff --git a/src/com/android/launcher3/DeferredHandler.java b/src/com/android/launcher3/DeferredHandler.java deleted file mode 100644 index a43ab6723..000000000 --- a/src/com/android/launcher3/DeferredHandler.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2008 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.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.MessageQueue; - -import com.android.launcher3.util.Thunk; - -import java.util.LinkedList; - -/** - * Queue of things to run on a looper thread. Items posted with {@link #post} will not - * be actually enqued on the handler until after the last one has run, to keep from - * starving the thread. - * - * This class is fifo. - */ -public class DeferredHandler { - @Thunk LinkedList<Runnable> mQueue = new LinkedList<>(); - private MessageQueue mMessageQueue = Looper.myQueue(); - private Impl mHandler = new Impl(); - - @Thunk class Impl extends Handler implements MessageQueue.IdleHandler { - public void handleMessage(Message msg) { - Runnable r; - synchronized (mQueue) { - if (mQueue.size() == 0) { - return; - } - r = mQueue.removeFirst(); - } - r.run(); - synchronized (mQueue) { - scheduleNextLocked(); - } - } - - public boolean queueIdle() { - handleMessage(null); - return false; - } - } - - private class IdleRunnable implements Runnable { - Runnable mRunnable; - - IdleRunnable(Runnable r) { - mRunnable = r; - } - - public void run() { - mRunnable.run(); - } - } - - public DeferredHandler() { - } - - /** Schedule runnable to run after everything that's on the queue right now. */ - public void post(Runnable runnable) { - synchronized (mQueue) { - mQueue.add(runnable); - if (mQueue.size() == 1) { - scheduleNextLocked(); - } - } - } - - /** Schedule runnable to run when the queue goes idle. */ - public void postIdle(final Runnable runnable) { - post(new IdleRunnable(runnable)); - } - - public void cancelAll() { - synchronized (mQueue) { - mQueue.clear(); - } - } - - /** Runs all queued Runnables from the calling thread. */ - public void flush() { - LinkedList<Runnable> queue = new LinkedList<>(); - synchronized (mQueue) { - queue.addAll(mQueue); - mQueue.clear(); - } - for (Runnable r : queue) { - r.run(); - } - } - - void scheduleNextLocked() { - if (mQueue.size() > 0) { - Runnable peek = mQueue.getFirst(); - if (peek instanceof IdleRunnable) { - mMessageQueue.addIdleHandler(mHandler); - } else { - mHandler.sendEmptyMessage(1); - } - } - } -} - diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java index b36734bab..fe7acda17 100644 --- a/src/com/android/launcher3/FocusHelper.java +++ b/src/com/android/launcher3/FocusHelper.java @@ -22,7 +22,7 @@ import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewGroup; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderPagedView; import com.android.launcher3.util.FocusLogic; @@ -93,7 +93,7 @@ public class FocusHelper { } if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new IllegalStateException("Parent of the focused item is not supported."); } else { return false; diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java index 8aeab8712..146c2eee7 100644 --- a/src/com/android/launcher3/InvariantDeviceProfile.java +++ b/src/com/android/launcher3/InvariantDeviceProfile.java @@ -28,7 +28,6 @@ import android.view.Display; import android.view.WindowManager; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.Thunk; @@ -335,7 +334,7 @@ public class InvariantDeviceProfile { } public int getAllAppsButtonRank() { - if (ProviderConfig.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) { + if (FeatureFlags.IS_DOGFOOD_BUILD && FeatureFlags.NO_ALL_APPS_ICON) { throw new IllegalAccessError("Accessing all apps rank when all-apps is disabled"); } return numHotseatIcons / 2; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5e08c2ab3..0a0859758 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -91,7 +91,6 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PinItemRequestCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; @@ -3387,7 +3386,7 @@ public class Launcher extends BaseActivity Object tag = v.getTag(); String desc = "Collision while binding workspace item: " + item + ". Collides with " + tag; - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw (new RuntimeException(desc)); } else { Log.d(TAG, desc); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 2e75579ca..f2d66fe81 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -26,7 +26,7 @@ import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.ConfigMonitor; @@ -38,7 +38,7 @@ import java.util.concurrent.ExecutionException; public class LauncherAppState { - public static final boolean PROFILE_STARTUP = ProviderConfig.IS_DOGFOOD_BUILD; + public static final boolean PROFILE_STARTUP = FeatureFlags.IS_DOGFOOD_BUILD; // We do not need any synchronization for this variable as its only written on UI thread. private static LauncherAppState INSTANCE; diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index dc668e6a4..35811d38a 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -44,10 +44,11 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; @@ -71,6 +72,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -109,9 +111,9 @@ public class LauncherModel extends BroadcastReceiver private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons private static final long INVALID_SCREEN_ID = -1L; + private final MainThreadExecutor mUiExecutor = new MainThreadExecutor(); @Thunk final LauncherAppState mApp; @Thunk final Object mLock = new Object(); - @Thunk DeferredHandler mHandler = new DeferredHandler(); @Thunk LoaderTask mLoaderTask; @Thunk boolean mIsLoaderTaskRunning; @Thunk boolean mHasLoaderCompletedOnce; @@ -127,6 +129,11 @@ public class LauncherModel extends BroadcastReceiver // our monitoring of the package manager provides all updates and we never // need to do a requery. This is only ever touched from the loader thread. private boolean mModelLoaded; + public boolean isModelLoaded() { + synchronized (mLock) { + return mModelLoaded && mLoaderTask == null; + } + } /** * Set of runnables to be called on the background thread after the workspace binding @@ -212,17 +219,6 @@ public class LauncherModel extends BroadcastReceiver mUserManager = UserManagerCompat.getInstance(context); } - /** Runs the specified runnable immediately if called from the main thread, otherwise it is - * posted on the main thread handler. */ - private void runOnMainThread(Runnable r) { - if (sWorkerThread.getThreadId() == Process.myTid()) { - // If we are on the worker thread, post onto the main handler - mHandler.post(r); - } else { - r.run(); - } - } - /** Runs the specified runnable immediately if called from the worker thread, otherwise it is * posted on the worker thread handler. */ private static void runOnWorkerThread(Runnable r) { @@ -372,8 +368,6 @@ public class LauncherModel extends BroadcastReceiver public void initialize(Callbacks callbacks) { synchronized (mLock) { Preconditions.assertUIThread(); - // Remove any queued UI runnables - mHandler.cancelAll(); mCallbacks = new WeakReference<>(callbacks); } } @@ -537,11 +531,11 @@ public class LauncherModel extends BroadcastReceiver if (mCallbacks != null && mCallbacks.get() != null) { final Callbacks oldCallbacks = mCallbacks.get(); // Clear any pending bind-runnables from the synchronized load process. - runOnMainThread(new Runnable() { - public void run() { - oldCallbacks.clearPendingBinds(); - } - }); + mUiExecutor.execute(new Runnable() { + public void run() { + oldCallbacks.clearPendingBinds(); + } + }); // If there is already one running, tell it to stop. stopLoaderLocked(); @@ -592,7 +586,6 @@ public class LauncherModel extends BroadcastReceiver @Thunk boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; - @Thunk boolean mLoadAndBindStepFinished; LoaderTask(Context context, int pageToBindFirst) { mContext = context; @@ -604,34 +597,10 @@ public class LauncherModel extends BroadcastReceiver // This way we don't start loading all apps until the workspace has settled // down. synchronized (LoaderTask.this) { - final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; - - mHandler.postIdle(new Runnable() { - public void run() { - synchronized (LoaderTask.this) { - mLoadAndBindStepFinished = true; - if (DEBUG_LOADERS) { - Log.d(TAG, "done with previous binding step"); - } - LoaderTask.this.notify(); - } - } - }); - - while (!mStopped && !mLoadAndBindStepFinished) { - try { - // Just in case mFlushingWorkerThread changes but we aren't woken up, - // wait no longer than 1sec at a time - this.wait(1000); - } catch (InterruptedException ex) { - // Ignore - } - } - if (DEBUG_LOADERS) { - Log.d(TAG, "waited " - + (SystemClock.uptimeMillis()-workspaceWaitTime) - + "ms for previous step to finish binding"); - } + LooperIdleLock idleLock = new LooperIdleLock(this, Looper.getMainLooper()); + // Just in case mFlushingWorkerThread changes but we aren't woken up, + // wait no longer than 1sec at a time + while (!mStopped && idleLock.awaitLocked(1000)); } } @@ -654,15 +623,6 @@ public class LauncherModel extends BroadcastReceiver } } - // XXX: Throw an exception if we are already loading (since we touch the worker thread - // data structures, we can't allow any other thread to touch that data, but because - // this call is synchronous, we can get away with not locking). - - // The LauncherModel is static in the LauncherAppState and mHandler may have queued - // operations from the previous activity. We need to ensure that all queued operations - // are executed before any synchronous binding work is done. - mHandler.flush(); - // Divide the set of loaded items into those that we are binding synchronously, and // everything else that is to be bound normally (asynchronously). bindWorkspace(synchronousBindPage); @@ -690,6 +650,7 @@ public class LauncherModel extends BroadcastReceiver } try { + long now = 0; if (DEBUG_LOADERS) Log.d(TAG, "step 1.1: loading workspace"); // Set to false in bindWorkspace() mIsLoadingAndBindingWorkspace = true; @@ -700,8 +661,12 @@ public class LauncherModel extends BroadcastReceiver bindWorkspace(mPageToBindFirst); // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 1 completed, wait for idle"); + if (DEBUG_LOADERS) { + Log.d(TAG, "step 1 completed, wait for idle"); + now = SystemClock.uptimeMillis(); + } waitForIdle(); + if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms"); verifyNotStopped(); // second step @@ -713,8 +678,12 @@ public class LauncherModel extends BroadcastReceiver updateIconCache(); // Take a break - if (DEBUG_LOADERS) Log.d(TAG, "step 2 completed, wait for idle"); + if (DEBUG_LOADERS) { + Log.d(TAG, "step 2 completed, wait for idle"); + now = SystemClock.uptimeMillis(); + } waitForIdle(); + if (DEBUG_LOADERS) Log.d(TAG, "Waited " + (SystemClock.uptimeMillis() - now) + "ms"); verifyNotStopped(); // third step @@ -883,6 +852,8 @@ public class LauncherModel extends BroadcastReceiver Intent intent; String targetPkg; + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); while (!mStopped && c.moveToNext()) { try { if (c.user == null) { @@ -1007,7 +978,7 @@ public class LauncherModel extends BroadcastReceiver } boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && - c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; + !verifier.isItemInPreview(c.getInt(rankIndex)); if (c.restoreFlag != 0) { // Already verified above that user is same as default user @@ -1259,17 +1230,23 @@ public class LauncherModel extends BroadcastReceiver } } - // Sort all the folder items and make sure the first 3 items are high resolution. + FolderIconPreviewVerifier verifier = + new FolderIconPreviewVerifier(mApp.getInvariantDeviceProfile()); + // Sort the folder items and make sure all items in the preview are high resolution. for (FolderInfo folder : sBgDataModel.folders) { Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR); - int pos = 0; + verifier.setFolderInfo(folder); + + int numItemsInPreview = 0; for (ShortcutInfo info : folder.contents) { - if (info.usingLowResIcon && - info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { + if (info.usingLowResIcon + && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION + && verifier.isItemInPreview(info.rank)) { mIconCache.getTitleAndIcon(info, false); + numItemsInPreview++; } - pos ++; - if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { + + if (numItemsInPreview >= FolderIcon.NUM_ITEMS_IN_PREVIEW) { break; } } @@ -1394,7 +1371,7 @@ public class LauncherModel extends BroadcastReceiver return Utilities.longCompare(lhs.screenId, rhs.screenId); } default: - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new RuntimeException("Unexpected container type when " + "sorting workspace items."); } @@ -1419,7 +1396,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } private void bindWorkspaceItems(final Callbacks oldCallbacks, @@ -1525,11 +1502,11 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); bindWorkspaceScreens(oldCallbacks, orderedScreenIds); - Executor mainExecutor = new DeferredMainThreadExecutor(); + Executor mainExecutor = mUiExecutor; // Load items on the current page. bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets, mainExecutor); @@ -1539,7 +1516,7 @@ public class LauncherModel extends BroadcastReceiver // This ensures that the first screen is immediately visible (eg. during rotation) // In case of !validFirstPage, bind all pages one after other. final Executor deferredExecutor = - validFirstPage ? new ViewOnDrawExecutor(mHandler) : mainExecutor; + validFirstPage ? new ViewOnDrawExecutor(mUiExecutor) : mainExecutor; mainExecutor.execute(new Runnable() { @Override @@ -1599,7 +1576,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } } @@ -1649,7 +1626,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } private void loadAllApps() { @@ -1697,21 +1674,21 @@ public class LauncherModel extends BroadcastReceiver heuristic.processUserApps(apps); } }; - runOnMainThread(new Runnable() { - - @Override - public void run() { - // Check isLoadingWorkspace on the UI thread, as it is updated on - // the UI thread. - if (mIsLoadingAndBindingWorkspace) { - synchronized (mBindCompleteRunnables) { - mBindCompleteRunnables.add(r); - } - } else { - runOnWorkerThread(r); - } - } - }); + mUiExecutor.execute(new Runnable() { + + @Override + public void run() { + // Check isLoadingWorkspace on the UI thread, as it is updated on + // the UI thread. + if (mIsLoadingAndBindingWorkspace) { + synchronized (mBindCompleteRunnables) { + mBindCompleteRunnables.add(r); + } + } else { + runOnWorkerThread(r); + } + } + }); } } // Huh? Shouldn't this be inside the Runnable below? @@ -1719,7 +1696,7 @@ public class LauncherModel extends BroadcastReceiver mBgAllAppsList.added = new ArrayList<AppInfo>(); // Post callback on main thread - mHandler.post(new Runnable() { + mUiExecutor.execute(new Runnable() { public void run() { final long bindTime = SystemClock.uptimeMillis(); @@ -1773,7 +1750,7 @@ public class LauncherModel extends BroadcastReceiver } } }; - runOnMainThread(r); + mUiExecutor.execute(r); } /** @@ -1824,12 +1801,12 @@ public class LauncherModel extends BroadcastReceiver public static abstract class BaseModelUpdateTask implements Runnable { private LauncherModel mModel; - private DeferredHandler mUiHandler; + private Executor mUiExecutor; /* package private */ void init(LauncherModel model) { mModel = model; - mUiHandler = mModel.mHandler; + mUiExecutor = mModel.mUiExecutor; } @Override @@ -1852,7 +1829,7 @@ public class LauncherModel extends BroadcastReceiver */ public final void scheduleCallbackTask(final CallbackTask task) { final Callbacks callbacks = mModel.getCallback(); - mUiHandler.post(new Runnable() { + mUiExecutor.execute(new Runnable() { public void run() { Callbacks cb = mModel.getCallback(); if (callbacks == cb && cb != null) { @@ -1897,7 +1874,7 @@ public class LauncherModel extends BroadcastReceiver private void bindWidgetsModel(final Callbacks callbacks) { final MultiHashMap<PackageItemInfo, WidgetItem> widgets = mBgWidgetsModel.getWidgetsMap().clone(); - mHandler.post(new Runnable() { + mUiExecutor.execute(new Runnable() { @Override public void run() { Callbacks cb = getCallback(); @@ -1954,14 +1931,6 @@ public class LauncherModel extends BroadcastReceiver } } - @Thunk class DeferredMainThreadExecutor implements Executor { - - @Override - public void execute(Runnable command) { - runOnMainThread(command); - } - } - /** * @return the looper for the worker thread which can be used to start background tasks. */ diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index e250b3f29..b83ddb927 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -53,7 +53,6 @@ import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.WorkspaceScreens; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.LauncherDbUtils; @@ -63,6 +62,8 @@ import com.android.launcher3.util.NoLocaleSqliteContext; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Thunk; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; @@ -74,7 +75,7 @@ public class LauncherProvider extends ContentProvider { private static final int DATABASE_VERSION = 27; - public static final String AUTHORITY = ProviderConfig.AUTHORITY; + public static final String AUTHORITY = (BuildConfig.APPLICATION_ID + ".settings").intern(); static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; @@ -85,9 +86,21 @@ public class LauncherProvider extends ContentProvider { protected DatabaseHelper mOpenHelper; + /** + * $ adb shell dumpsys activity provider com.android.launcher3 + */ + @Override + public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); + if (appState == null || !appState.getModel().isModelLoaded()) { + return; + } + appState.getModel().dumpState("", fd, writer, args); + } + @Override public boolean onCreate() { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.d(TAG, "Launcher process started"); } mListenerHandler = new Handler(mListenerWrapper); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index af2c10275..e8e0eb234 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -22,8 +22,6 @@ import android.net.Uri; import android.os.Bundle; import android.provider.BaseColumns; -import com.android.launcher3.config.ProviderConfig; - /** * Settings related utilities. */ @@ -101,7 +99,7 @@ public class LauncherSettings { * The content:// style URL for this table */ public static final Uri CONTENT_URI = Uri.parse("content://" + - ProviderConfig.AUTHORITY + "/" + TABLE_NAME); + LauncherProvider.AUTHORITY + "/" + TABLE_NAME); /** * The rank of this screen -- ie. how it is ordered relative to the other screens. @@ -121,7 +119,7 @@ public class LauncherSettings { * The content:// style URL for this table */ public static final Uri CONTENT_URI = Uri.parse("content://" + - ProviderConfig.AUTHORITY + "/" + TABLE_NAME); + LauncherProvider.AUTHORITY + "/" + TABLE_NAME); /** * The content:// style URL for a given row, identified by its id. @@ -131,7 +129,7 @@ public class LauncherSettings { * @return The unique content URL for the specified row. */ public static Uri getContentUri(long id) { - return Uri.parse("content://" + ProviderConfig.AUTHORITY + + return Uri.parse("content://" + LauncherProvider.AUTHORITY + "/" + TABLE_NAME + "/" + id); } @@ -280,7 +278,7 @@ public class LauncherSettings { public static final class Settings { public static final Uri CONTENT_URI = Uri.parse("content://" + - ProviderConfig.AUTHORITY + "/settings"); + LauncherProvider.AUTHORITY + "/settings"); public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag"; public static final String METHOD_WAS_EMPTY_DB_CREATED = "get_empty_db_flag"; diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index 39c466db8..f5af979ce 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -32,7 +32,7 @@ import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.util.CircleRevealOutlineProvider; +import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; diff --git a/src/com/android/launcher3/MainThreadExecutor.java b/src/com/android/launcher3/MainThreadExecutor.java index 4ca0a59d8..509468233 100644 --- a/src/com/android/launcher3/MainThreadExecutor.java +++ b/src/com/android/launcher3/MainThreadExecutor.java @@ -18,14 +18,14 @@ package com.android.launcher3; import android.os.Looper; -import com.android.launcher3.util.LooperExecuter; +import com.android.launcher3.util.LooperExecutor; /** * An executor service that executes its tasks on the main thread. * * Shutting down this executor is not supported. */ -public class MainThreadExecutor extends LooperExecuter { +public class MainThreadExecutor extends LooperExecutor { public MainThreadExecutor() { super(Looper.getMainLooper()); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index abc53673b..2413d8ae6 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -51,7 +51,7 @@ import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -575,7 +575,7 @@ public final class Utilities { try { c.close(); } catch (IOException e) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.d(TAG, "Error closing", e); } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 7562dd8b3..862ed25f9 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -64,7 +64,6 @@ import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.badge.FolderBadgeInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; @@ -2619,7 +2618,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(cell); if (parentCell != null) { parentCell.removeView(cell); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new NullPointerException("mDragInfo.cell has null parent"); } addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1], @@ -2952,7 +2951,7 @@ public class Workspace extends PagedView ItemInfo item = d.dragInfo; if (item == null) { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new NullPointerException("DragObject has null info"); } return; @@ -3609,7 +3608,7 @@ public class Workspace extends PagedView mDragInfo.container, mDragInfo.screenId); if (cellLayout != null) { cellLayout.onDropChild(mDragInfo.cell); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { throw new RuntimeException("Invalid state: cellLayout == null in " + "Workspace#onDropCompleted. Please file a bug. "); }; @@ -3635,7 +3634,7 @@ public class Workspace extends PagedView CellLayout parentCell = getParentCellLayoutForView(v); if (parentCell != null) { parentCell.removeView(v); - } else if (ProviderConfig.IS_DOGFOOD_BUILD) { + } else if (FeatureFlags.IS_DOGFOOD_BUILD) { // When an app is uninstalled using the drop target, we wait until resume to remove // the icon. We also remove all the corresponding items from the workspace at // {@link Launcher#bindComponentsRemoved}. That call can come before or after diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java index f228470f4..9e32d257d 100644 --- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java +++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java @@ -24,7 +24,7 @@ import android.util.Log; import com.android.launcher3.AppInfo; import com.android.launcher3.Launcher; import com.android.launcher3.compat.AlphabeticIndexCompat; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.discovery.AppDiscoveryAppInfo; import com.android.launcher3.discovery.AppDiscoveryItem; import com.android.launcher3.discovery.AppDiscoveryUpdateState; @@ -440,7 +440,7 @@ public class AlphabeticalAppsList { if (info != null) { mPredictedApps.add(info); } else { - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { Log.e(TAG, "Predicted app not found: " + ck); } } diff --git a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java index 9fe51476d..9fb6b498b 100644 --- a/src/com/android/launcher3/util/CircleRevealOutlineProvider.java +++ b/src/com/android/launcher3/anim/CircleRevealOutlineProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.util; +package com.android.launcher3.anim; public class CircleRevealOutlineProvider extends RevealOutlineAnimation { diff --git a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java index be1e2d644..679e8e32f 100644 --- a/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java +++ b/src/com/android/launcher3/anim/PillHeightRevealOutlineProvider.java @@ -18,8 +18,6 @@ package com.android.launcher3.anim; import android.graphics.Rect; -import com.android.launcher3.util.PillRevealOutlineProvider; - /** * Extension of {@link PillRevealOutlineProvider} which only changes the height of the pill. * For now, we assume the height is added/removed from the bottom. diff --git a/src/com/android/launcher3/util/PillRevealOutlineProvider.java b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java index a57d69fab..450f9db9a 100644 --- a/src/com/android/launcher3/util/PillRevealOutlineProvider.java +++ b/src/com/android/launcher3/anim/PillRevealOutlineProvider.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.util; +package com.android.launcher3.anim; import android.graphics.Rect; import android.view.ViewOutlineProvider; diff --git a/src/com/android/launcher3/util/RevealOutlineAnimation.java b/src/com/android/launcher3/anim/RevealOutlineAnimation.java index 456047775..51d00d947 100644 --- a/src/com/android/launcher3/util/RevealOutlineAnimation.java +++ b/src/com/android/launcher3/anim/RevealOutlineAnimation.java @@ -1,4 +1,4 @@ -package com.android.launcher3.util; +package com.android.launcher3.anim; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -83,4 +83,8 @@ public abstract class RevealOutlineAnimation extends ViewOutlineProvider { public void getOutline(View v, Outline outline) { outline.setRoundRect(mOutline, mOutlineRadius); } + + public float getRadius() { + return mOutlineRadius; + } } diff --git a/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java new file mode 100644 index 000000000..9c09477bb --- /dev/null +++ b/src/com/android/launcher3/anim/RoundedRectRevealOutlineProvider.java @@ -0,0 +1,57 @@ +/* + * 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.anim; + +import android.graphics.Rect; + +/** + * A {@link RevealOutlineAnimation} that provides an outline that interpolates between two radii + * and two {@link Rect}s. + * + * An example usage of this provider is an outline that starts out as a circle and ends + * as a rounded rectangle. + */ +public class RoundedRectRevealOutlineProvider extends RevealOutlineAnimation { + private final float mStartRadius; + private final float mEndRadius; + + private final Rect mStartRect; + private final Rect mEndRect; + + public RoundedRectRevealOutlineProvider(float startRadius, float endRadius, Rect startRect, + Rect endRect) { + mStartRadius = startRadius; + mEndRadius = endRadius; + mStartRect = startRect; + mEndRect = endRect; + } + + @Override + public boolean shouldRemoveElevationDuringAnimation() { + return false; + } + + @Override + public void setProgress(float progress) { + mOutlineRadius = (1 - progress) * mStartRadius + progress * mEndRadius; + + mOutline.left = (int) ((1 - progress) * mStartRect.left + progress * mEndRect.left); + mOutline.top = (int) ((1 - progress) * mStartRect.top + progress * mEndRect.top); + mOutline.right = (int) ((1 - progress) * mStartRect.right + progress * mEndRect.right); + mOutline.bottom = (int) ((1 - progress) * mStartRect.bottom + progress * mEndRect.bottom); + } +} diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java index 840fcf5fe..503c2ec9f 100644 --- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java @@ -121,6 +121,11 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule } @Override + public float getIconSize() { + return mIconSize; + } + + @Override public int maxNumItems() { return MAX_NUM_ITEMS_IN_PREVIEW; } @@ -129,24 +134,4 @@ public class ClippedFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule public boolean clipToBackground() { return true; } - - @Override - public List<View> getItemsToDisplay(Folder folder) { - List<View> items = new ArrayList<>(folder.getItemsInReadingOrder()); - int numItems = items.size(); - if (FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION && numItems > MAX_NUM_ITEMS_IN_PREVIEW) { - // We match the icons in the preview with the layout of the opened folder (b/27944225), - // but we still need to figure out how we want to handle updating the preview when the - // upper left quadrant changes. - int appsPerRow = folder.mContent.getPageAt(0).getCountX(); - int appsToDelete = appsPerRow - MAX_NUM_ITEMS_PER_ROW; - - // We only display the upper left quadrant. - while (appsToDelete > 0) { - items.remove(MAX_NUM_ITEMS_PER_ROW); - appsToDelete--; - } - } - return items.subList(0, Math.min(numItems, MAX_NUM_ITEMS_IN_PREVIEW)); - } } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 3d2ffb4ff..15bdea986 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -46,6 +46,7 @@ import android.widget.TextView; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Alarm; import com.android.launcher3.AppInfo; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; @@ -66,8 +67,8 @@ import com.android.launcher3.UninstallDropTarget.DropTargetSource; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragLayer; @@ -75,12 +76,13 @@ import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; -import com.android.launcher3.util.CircleRevealOutlineProvider; +import com.android.launcher3.anim.CircleRevealOutlineProvider; import com.android.launcher3.util.Thunk; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; /** * Represents a set of icons chosen by the user or generated by the system. @@ -133,8 +135,10 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC @Thunk final ArrayList<View> mItemsInReadingOrder = new ArrayList<View>(); + private FolderAnimationManager mFolderAnimationManager; + private final int mExpandDuration; - private final int mMaterialExpandDuration; + public final int mMaterialExpandDuration; private final int mMaterialExpandStagger; protected final Launcher mLauncher; @@ -475,6 +479,8 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } } }); + + mFolderAnimationManager = new FolderAnimationManager(this); } /** @@ -510,51 +516,12 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mState = STATE_SMALL; } - /** - * Opens the user folder described by the specified tag. The opening of the folder - * is animated relative to the specified View. If the View is null, no animation - * is played. - */ - public void animateOpen() { - Folder openFolder = getOpen(mLauncher); - if (openFolder != null && openFolder != this) { - // Close any open folder before opening a folder. - openFolder.close(true); - } - - DragLayer dragLayer = mLauncher.getDragLayer(); - // Just verify that the folder hasn't already been added to the DragLayer. - // There was a one-off crash where the folder had a parent already. - if (getParent() == null) { - dragLayer.addView(this); - mDragController.addDropTarget(this); - } else { - if (ProviderConfig.IS_DOGFOOD_BUILD) { - Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" - + getParent()); - } - } - - mIsOpen = true; - - mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } - - // This is set to true in close(), but isn't reset to false until onDropCompleted(). This - // leads to an inconsistent state if you drag out of the folder and drag back in without - // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. - mDeleteFolderOnDropCompleted = false; - - final Runnable onCompleteRunnable; + private AnimatorSet getOpeningAnimatorSet() { prepareReveal(); - centerAboutIcon(); - mFolderIcon.growAndFadeOut(); AnimatorSet anim = LauncherAnimUtils.createAnimatorSet(); + int width = getFolderWidth(); int height = getFolderHeight(); @@ -596,13 +563,61 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC anim.play(textAlpha); anim.play(reveal); - mContent.setLayerType(LAYER_TYPE_HARDWARE, null); - mFooter.setLayerType(LAYER_TYPE_HARDWARE, null); + AnimationLayerSet layerSet = new AnimationLayerSet(); + layerSet.addView(mContent); + layerSet.addView(mFooter); + anim.addListener(layerSet); + + return anim; + } + + /** + * Opens the user folder described by the specified tag. The opening of the folder + * is animated relative to the specified View. If the View is null, no animation + * is played. + */ + public void animateOpen() { + Folder openFolder = getOpen(mLauncher); + if (openFolder != null && openFolder != this) { + // Close any open folder before opening a folder. + openFolder.close(true); + } + + DragLayer dragLayer = mLauncher.getDragLayer(); + // Just verify that the folder hasn't already been added to the DragLayer. + // There was a one-off crash where the folder had a parent already. + if (getParent() == null) { + dragLayer.addView(this); + mDragController.addDropTarget(this); + } else { + if (FeatureFlags.IS_DOGFOOD_BUILD) { + Log.e(TAG, "Opening folder (" + this + ") which already has a parent:" + + getParent()); + } + } + + mIsOpen = true; + + mContent.completePendingPageChanges(); + if (!mDragInProgress) { + // Open on the first page. + mContent.snapToPageImmediately(0); + } + + // This is set to true in close(), but isn't reset to false until onDropCompleted(). This + // leads to an inconsistent state if you drag out of the folder and drag back in without + // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice. + mDeleteFolderOnDropCompleted = false; + + final Runnable onCompleteRunnable; + centerAboutIcon(); + + AnimatorSet anim = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + ? mFolderAnimationManager.getOpeningAnimator() + : getOpeningAnimatorSet(); onCompleteRunnable = new Runnable() { @Override public void run() { - mContent.setLayerType(LAYER_TYPE_NONE, null); - mFooter.setLayerType(LAYER_TYPE_NONE, null); mLauncher.getUserEventDispatcher().resetElapsedContainerMillis(); } }; @@ -697,7 +712,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mFolderName.dispatchBackKey(); } - if (mFolderIcon != null) { + if (mFolderIcon != null && !FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION) { mFolderIcon.shrinkAndFadeIn(animate); } @@ -715,12 +730,24 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC parent.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } + private AnimatorSet getClosingAnimatorSet() { + AnimatorSet animatorSet = LauncherAnimUtils.createAnimatorSet(); + animatorSet.play(LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f)); + + AnimationLayerSet layerSet = new AnimationLayerSet(); + layerSet.addView(this); + animatorSet.addListener(layerSet); + animatorSet.setDuration(mExpandDuration); + return animatorSet; + } + private void animateClosed() { - final ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(this, 0, 0.9f, 0.9f); - oa.addListener(new AnimatorListenerAdapter() { + AnimatorSet a = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + ? mFolderAnimationManager.getClosingAnimator() + : getClosingAnimatorSet(); + a.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - setLayerType(LAYER_TYPE_NONE, null); closeComplete(true); } @Override @@ -732,9 +759,7 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC mState = STATE_ANIMATING; } }); - oa.setDuration(mExpandDuration); - setLayerType(LAYER_TYPE_HARDWARE, null); - oa.start(); + a.start(); } private void closeComplete(boolean wasAnimated) { @@ -745,10 +770,14 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC } mDragController.removeDropTarget(this); clearFocus(); - if (wasAnimated) { - mFolderIcon.requestFocus(); + if (mFolderIcon != null) { + mFolderIcon.setVisibility(View.VISIBLE); + if (wasAnimated) { + mFolderIcon.requestFocus(); + } } + if (mRearrangeOnClose) { rearrangeChildren(); mRearrangeOnClose = false; @@ -1436,6 +1465,26 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC return mItemsInReadingOrder; } + public List<BubbleTextView> getItemsOnCurrentPage() { + ArrayList<View> allItems = getItemsInReadingOrder(); + int currentPage = mContent.getCurrentPage(); + int lastPage = mContent.getPageCount() - 1; + int totalItemsInFolder = allItems.size(); + int itemsPerPage = mContent.itemsPerPage(); + int numItemsOnCurrentPage = currentPage == lastPage + ? totalItemsInFolder - (itemsPerPage * currentPage) + : itemsPerPage; + + int startIndex = currentPage * itemsPerPage; + int endIndex = startIndex + numItemsOnCurrentPage; + + List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage); + for (int i = startIndex; i < endIndex; ++i) { + itemsOnCurrentPage.add((BubbleTextView) allItems.get(i)); + } + return itemsOnCurrentPage; + } + public void onFocusChange(View v, boolean hasFocus) { if (v == mFolderName) { if (hasFocus) { diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java new file mode 100644 index 000000000..6ce572d94 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderAnimationManager.java @@ -0,0 +1,357 @@ +/* + * 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.folder; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.drawable.GradientDrawable; +import android.support.v4.graphics.ColorUtils; +import android.util.Property; +import android.view.View; +import android.view.animation.AnimationUtils; + +import com.android.launcher3.BubbleTextView; +import com.android.launcher3.CellLayout; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.R; +import com.android.launcher3.ShortcutAndWidgetContainer; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.util.Themes; + +import java.util.List; + +/** + * Manages the opening and closing animations for a {@link Folder}. + * + * All of the animations are done in the Folder. + * ie. When the user taps on the FolderIcon, we immediately hide the FolderIcon and show the Folder + * in its place before starting the animation. + */ +public class FolderAnimationManager { + + private Folder mFolder; + private FolderPagedView mContent; + private GradientDrawable mFolderBackground; + + private FolderIcon mFolderIcon; + private FolderIcon.PreviewBackground mPreviewBackground; + + private Context mContext; + private Launcher mLauncher; + + private Animator mRevealAnimator; + private final TimeInterpolator mOpeningInterpolator; + private final TimeInterpolator mClosingInterpolator; + private final TimeInterpolator mPreviewItemOpeningInterpolator; + private final TimeInterpolator mPreviewItemClosingInterpolator; + + private final FolderIcon.PreviewItemDrawingParams mTmpParams = + new FolderIcon.PreviewItemDrawingParams(0, 0, 0, 0); + + private static final Property<View, Float> SCALE_PROPERTY = + new Property<View, Float>(Float.class, "scale") { + @Override + public Float get(View view) { + return view.getScaleX(); + } + + @Override + public void set(View view, Float scale) { + view.setScaleX(scale); + view.setScaleY(scale); + } + }; + + private static final Property<List<BubbleTextView>, Integer> ITEMS_TEXT_COLOR_PROPERTY = + new Property<List<BubbleTextView>, Integer>(Integer.class, "textColor") { + @Override + public Integer get(List<BubbleTextView> items) { + return items.get(0).getCurrentTextColor(); + } + + @Override + public void set(List<BubbleTextView> items, Integer color) { + int size = items.size(); + + for (int i = 0; i < size; ++i) { + items.get(i).setTextColor(color); + } + } + }; + + public FolderAnimationManager(Folder folder) { + mFolder = folder; + mContent = folder.mContent; + mFolderBackground = (GradientDrawable) mFolder.getBackground(); + + mFolderIcon = folder.mFolderIcon; + mPreviewBackground = mFolderIcon.mBackground; + + mContext = folder.getContext(); + mLauncher = folder.mLauncher; + + mOpeningInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_opening_interpolator); + mClosingInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_closing_interpolator); + mPreviewItemOpeningInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_preview_item_opening_interpolator); + mPreviewItemClosingInterpolator = AnimationUtils.loadInterpolator(mContext, + R.interpolator.folder_preview_item_closing_interpolator); + } + + public AnimatorSet getOpeningAnimator() { + mFolder.setPivotX(0); + mFolder.setPivotY(0); + + AnimatorSet a = getAnimatorSet(true /* isOpening */); + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mFolderIcon.setVisibility(View.INVISIBLE); + } + }); + return a; + } + + public AnimatorSet getClosingAnimator() { + AnimatorSet a = getAnimatorSet(false /* isOpening */); + return a; + } + + /** + * Prepares the Folder for animating between open / closed states. + * + * @param isOpening If true, return the animator set for the opening animation. + */ + private AnimatorSet getAnimatorSet(final boolean isOpening) { + final DragLayer.LayoutParams lp = (DragLayer.LayoutParams) mFolder.getLayoutParams(); + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + + // Match size/scale of icons in the preview + float previewScale = rule.scaleForItem(0, itemsInPreview.size()); + float previewSize = rule.getIconSize() * previewScale; + float folderScale = previewSize / itemsInPreview.get(0).getIconSize(); + + final float initialScale = folderScale; + final float finalScale = 1f; + float scale = isOpening ? initialScale : finalScale; + mFolder.setScaleX(scale); + mFolder.setScaleY(scale); + + // Match position of the FolderIcon + final Rect folderIconPos = new Rect(); + float scaleRelativeToDragLayer = mLauncher.getDragLayer() + .getDescendantRectRelativeToSelf(mFolderIcon, folderIconPos); + folderScale *= scaleRelativeToDragLayer; + + // We want to create a small X offset for the preview items, so that they follow their + // expected path to their final locations. ie. an icon should not move right, if it's final + // location is to its left. This value is arbitrarily defined. + final int nudgeOffsetX = (int) (previewSize / 2); + + final int paddingOffsetX = (int) ((mFolder.getPaddingLeft() + mContent.getPaddingLeft()) + * folderScale); + final int paddingOffsetY = (int) ((mFolder.getPaddingTop() + mContent.getPaddingTop()) + * folderScale); + + int initialX = folderIconPos.left + mFolderIcon.mBackground.getOffsetX() - paddingOffsetX + - nudgeOffsetX; + int initialY = folderIconPos.top + mFolderIcon.mBackground.getOffsetY() - paddingOffsetY; + final float xDistance = initialX - lp.x; + final float yDistance = initialY - lp.y; + + // Set up the Folder background. + final int finalColor = Themes.getAttrColor(mContext, android.R.attr.colorPrimary); + final int initialColor = + ColorUtils.setAlphaComponent(finalColor, mPreviewBackground.getBackgroundAlpha()); + mFolderBackground.setColor(isOpening ? initialColor : finalColor); + + // Initialize the Folder items' text. + final List<BubbleTextView> itemsOnCurrentPage = mFolder.getItemsOnCurrentPage(); + final int finalTextColor = Themes.getAttrColor(mContext, android.R.attr.textColorSecondary); + ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, isOpening ? Color.TRANSPARENT + : finalTextColor); + + // Create the animators. + AnimatorSet a = LauncherAnimUtils.createAnimatorSet(); + a.setDuration(mFolder.mMaterialExpandDuration); + + ObjectAnimator translationX = isOpening + ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, xDistance, 0) + : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_X, 0, xDistance); + a.play(translationX); + + ObjectAnimator translationY = isOpening + ? ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, yDistance, 0) + : ObjectAnimator.ofFloat(mFolder, View.TRANSLATION_Y, 0, yDistance); + a.play(translationY); + + ObjectAnimator scaleAnimator = isOpening + ? ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, initialScale, finalScale) + : ObjectAnimator.ofFloat(mFolder, SCALE_PROPERTY, finalScale, initialScale); + a.play(scaleAnimator); + + ObjectAnimator itemsTextColor = isOpening + ? ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY, + Color.TRANSPARENT, finalTextColor) + : ObjectAnimator.ofArgb(itemsOnCurrentPage, ITEMS_TEXT_COLOR_PROPERTY, + finalTextColor, Color.TRANSPARENT); + a.play(itemsTextColor); + + ObjectAnimator backgroundColor = isOpening + ? ObjectAnimator.ofArgb(mFolderBackground, "color", initialColor, finalColor) + : ObjectAnimator.ofArgb(mFolderBackground, "color", finalColor, initialColor); + a.play(backgroundColor); + + // Set up the reveal animation that clips the Folder. + float stroke = mPreviewBackground.getStrokeWidth(); + int initialSize = (int) ((mFolderIcon.mBackground.getRadius() * 2 + stroke) / folderScale); + int totalOffsetX = paddingOffsetX + Math.round(nudgeOffsetX / folderScale); + int unscaledStroke = (int) Math.floor(stroke / folderScale); + Rect startRect = new Rect(totalOffsetX + unscaledStroke, unscaledStroke, + totalOffsetX + initialSize, initialSize); + Rect endRect = new Rect(0, 0, lp.width, lp.height); + a.play(getRevealAnimator(isOpening, initialSize / 2f, startRect, endRect)); + + a.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + ITEMS_TEXT_COLOR_PROPERTY.set(itemsOnCurrentPage, finalTextColor); + } + }); + + // We set the interpolator on all current child animators here, because the preview item + // animators may use a different interpolator. + for (Animator animator : a.getChildAnimations()) { + animator.setInterpolator(isOpening ? mOpeningInterpolator : mClosingInterpolator); + } + + addPreviewItemAnimatorsToSet(a, isOpening, folderScale, nudgeOffsetX); + return a; + } + + private Animator getRevealAnimator(boolean isOpening, float circleRadius, Rect start, + Rect end) { + boolean revealIsRunning = mRevealAnimator != null && mRevealAnimator.isRunning(); + final float finalRadius = revealIsRunning + ? ((RoundedRectRevealOutlineProvider) mFolder.getOutlineProvider()).getRadius() + : Utilities.pxFromDp(2, mContext.getResources().getDisplayMetrics()); + if (revealIsRunning) { + mRevealAnimator.cancel(); + } + mRevealAnimator = new RoundedRectRevealOutlineProvider(circleRadius, finalRadius, + start, end).createRevealAnimator(mFolder, !isOpening); + return mRevealAnimator; + } + + /** + * Animate the items that are displayed in the preview. + */ + private void addPreviewItemAnimatorsToSet(AnimatorSet animatorSet, boolean isOpening, + final float folderScale, int nudgeOffsetX) { + FolderIcon.PreviewLayoutRule rule = mFolderIcon.getLayoutRule(); + final List<BubbleTextView> itemsInPreview = mFolderIcon.getItemsToDisplay(); + final int numItemsInPreview = itemsInPreview.size(); + + TimeInterpolator previewItemInterpolator = getPreviewItemInterpolator(isOpening); + + ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets(); + for (int i = 0; i < numItemsInPreview; ++i) { + final BubbleTextView btv = itemsInPreview.get(i); + CellLayout.LayoutParams btvLp = (CellLayout.LayoutParams) btv.getLayoutParams(); + + // Calculate the final values in the LayoutParams. + btvLp.isLockedToGrid = true; + cwc.setupLp(btv); + + // Match scale of icons in the preview. + float previewScale = rule.scaleForItem(i, numItemsInPreview); + float previewSize = rule.getIconSize() * previewScale; + float iconScale = previewSize / itemsInPreview.get(i).getIconSize(); + + final float initialScale = iconScale / folderScale; + final float finalScale = 1f; + float scale = isOpening ? initialScale : finalScale; + btv.setScaleX(scale); + btv.setScaleY(scale); + + // Match positions of the icons in the folder with their positions in the preview + rule.computePreviewItemDrawingParams(i, numItemsInPreview, mTmpParams); + // The PreviewLayoutRule assumes that the icon size takes up the entire width so we + // offset by the actual size. + int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2; + + final int previewPosX = + (int) ((mTmpParams.transX - iconOffsetX + nudgeOffsetX) / folderScale); + final int previewPosY = (int) (mTmpParams.transY / folderScale); + + final float xDistance = previewPosX - btvLp.x; + final float yDistance = previewPosY - btvLp.y; + + ObjectAnimator translationX = isOpening + ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, xDistance, 0) + : ObjectAnimator.ofFloat(btv, View.TRANSLATION_X, 0, xDistance); + translationX.setInterpolator(previewItemInterpolator); + animatorSet.play(translationX); + + ObjectAnimator translationY = isOpening + ? ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, yDistance, 0) + : ObjectAnimator.ofFloat(btv, View.TRANSLATION_Y, 0, yDistance); + translationY.setInterpolator(previewItemInterpolator); + animatorSet.play(translationY); + + ObjectAnimator scaleAnimator = isOpening + ? ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, initialScale, finalScale) + : ObjectAnimator.ofFloat(btv, SCALE_PROPERTY, finalScale, initialScale); + scaleAnimator.setInterpolator(previewItemInterpolator); + animatorSet.play(scaleAnimator); + + animatorSet.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + btv.setTranslationX(0.0f); + btv.setTranslationY(0.0f); + btv.setScaleX(1f); + btv.setScaleY(1f); + } + }); + } + } + + private TimeInterpolator getPreviewItemInterpolator(boolean isOpening) { + if (mFolder.getItemCount() > FolderIcon.NUM_ITEMS_IN_PREVIEW) { + // With larger folders, we want the preview items to reach their final positions faster + // (when opening) and later (when closing) so that they appear aligned with the rest of + // the folder items when they are both visible. + return isOpening ? mPreviewItemOpeningInterpolator : mPreviewItemClosingInterpolator; + } + return isOpening ? mOpeningInterpolator : mClosingInterpolator; + } +} diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index f21601092..9395572bf 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -125,6 +125,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private float mSlop; + FolderIconPreviewVerifier mPreviewVerifier; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>(); private Drawable mReferenceDrawable = null; @@ -219,6 +220,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void setFolder(Folder folder) { mFolder = folder; + mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv); updateItemDrawingParams(false); } @@ -405,6 +407,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBadgeInfo = badgeInfo; } + public PreviewLayoutRule getLayoutRule() { + return mPreviewLayoutRule; + } + /** * Sets mBadgeScale to 1 or 0, animating if oldCount or newCount is 0 * (the badge is being added or removed). @@ -822,6 +828,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { }; animateScale(1f, 1f, onStart, onEnd); } + + public int getBackgroundAlpha() { + return (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier); + } + + public float getStrokeWidth() { + return mStrokeWidth; + } } public void setFolderBackground(PreviewBackground bg) { @@ -989,8 +1003,26 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mFolderName.getVisibility() == VISIBLE; } + public List<BubbleTextView> getItemsToDisplay() { + mPreviewVerifier.setFolderInfo(mFolder.getInfo()); + + List<BubbleTextView> itemsToDisplay = new ArrayList<>(); + List<View> allItems = mFolder.getItemsInReadingOrder(); + int numItems = allItems.size(); + for (int rank = 0; rank < numItems; ++rank) { + if (mPreviewVerifier.isItemInPreview(rank)) { + itemsToDisplay.add((BubbleTextView) allItems.get(rank)); + } + + if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) { + break; + } + } + return itemsToDisplay; + } + private void updateItemDrawingParams(boolean animate) { - List<View> items = mPreviewLayoutRule.getItemsToDisplay(mFolder); + List<BubbleTextView> items = getItemsToDisplay(); int nItemsInPreview = items.size(); int prevNumItems = mDrawingParams.size(); @@ -1005,7 +1037,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { for (int i = 0; i < mDrawingParams.size(); i++) { PreviewItemDrawingParams p = mDrawingParams.get(i); - p.drawable = ((TextView) items.get(i)).getCompoundDrawables()[1]; + p.drawable = items.get(i).getCompoundDrawables()[1]; if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) { computePreviewItemDrawingParams(i, nItemsInPreview, p); @@ -1174,8 +1206,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { PreviewItemDrawingParams params); void init(int availableSpace, int intrinsicIconSize, boolean rtl); float scaleForItem(int index, int totalNumItems); + float getIconSize(); int maxNumItems(); boolean clipToBackground(); - List<View> getItemsToDisplay(Folder folder); } } diff --git a/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java new file mode 100644 index 000000000..de962b021 --- /dev/null +++ b/src/com/android/launcher3/folder/FolderIconPreviewVerifier.java @@ -0,0 +1,64 @@ +/* + * 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.folder; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.config.FeatureFlags; + +/** + * Verifies whether an item in a Folder is displayed in the FolderIcon preview. + */ +public class FolderIconPreviewVerifier { + + private final int mMaxGridCountX; + private final int mMaxGridCountY; + private final int mMaxItemsPerPage; + private final int[] mGridSize = new int[2]; + + private int mGridCountX; + private boolean mDisplayingUpperLeftQuadrant = false; + + public FolderIconPreviewVerifier(InvariantDeviceProfile profile) { + mMaxGridCountX = profile.numFolderColumns; + mMaxGridCountY = profile.numFolderRows; + mMaxItemsPerPage = mMaxGridCountX * mMaxGridCountY; + } + + public void setFolderInfo(FolderInfo info) { + int numItemsInFolder = info.contents.size(); + mDisplayingUpperLeftQuadrant = FeatureFlags.LAUNCHER3_NEW_FOLDER_ANIMATION + && !FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON + && numItemsInFolder > FolderIcon.NUM_ITEMS_IN_PREVIEW; + + if (mDisplayingUpperLeftQuadrant) { + FolderPagedView.calculateGridSize(info.contents.size(), 0, 0, mMaxGridCountX, + mMaxGridCountY, mMaxItemsPerPage, mGridSize); + mGridCountX = mGridSize[0]; + } + } + + public boolean isItemInPreview(int rank) { + if (mDisplayingUpperLeftQuadrant) { + // Returns true iff the icon is in the 2x2 upper left quadrant of the Folder. + int col = rank % mGridCountX; + int row = rank / mGridCountX; + return col < 2 && row < 2; + } + return rank < FolderIcon.NUM_ITEMS_IN_PREVIEW; + } +} diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index eecce183a..bc78324e7 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -42,7 +42,6 @@ import com.android.launcher3.ShortcutAndWidgetContainer; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.Workspace.ItemOperator; -import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicator; import com.android.launcher3.util.Themes; @@ -68,7 +67,7 @@ public class FolderPagedView extends PagedView { */ private static final float SCROLL_HINT_FRACTION = 0.07f; - private static final int[] sTempPosArray = new int[2]; + private static final int[] sTmpArray = new int[2]; public final boolean mIsRtl; @@ -120,40 +119,58 @@ public class FolderPagedView extends PagedView { } /** - * Sets up the grid size such that {@param count} items can fit in the grid. + * Calculates the grid size such that {@param count} items can fit in the grid. * The grid size is calculated such that countY <= countX and countX = ceil(sqrt(count)) while * maintaining the restrictions of {@link #mMaxCountX} & {@link #mMaxCountY}. */ - private void setupContentDimensions(int count) { - mAllocatedContentSize = count; + public static void calculateGridSize(int count, int countX, int countY, int maxCountX, + int maxCountY, int maxItemsPerPage, int[] out) { boolean done; - if (count >= mMaxItemsPerPage) { - mGridCountX = mMaxCountX; - mGridCountY = mMaxCountY; + int gridCountX = countX; + int gridCountY = countY; + + if (count >= maxItemsPerPage) { + gridCountX = maxCountX; + gridCountY = maxCountY; done = true; } else { done = false; } while (!done) { - int oldCountX = mGridCountX; - int oldCountY = mGridCountY; - if (mGridCountX * mGridCountY < count) { + int oldCountX = gridCountX; + int oldCountY = gridCountY; + if (gridCountX * gridCountY < count) { // Current grid is too small, expand it - if ((mGridCountX <= mGridCountY || mGridCountY == mMaxCountY) && mGridCountX < mMaxCountX) { - mGridCountX++; - } else if (mGridCountY < mMaxCountY) { - mGridCountY++; + if ((gridCountX <= gridCountY || gridCountY == maxCountY) + && gridCountX < maxCountX) { + gridCountX++; + } else if (gridCountY < maxCountY) { + gridCountY++; } - if (mGridCountY == 0) mGridCountY++; - } else if ((mGridCountY - 1) * mGridCountX >= count && mGridCountY >= mGridCountX) { - mGridCountY = Math.max(0, mGridCountY - 1); - } else if ((mGridCountX - 1) * mGridCountY >= count) { - mGridCountX = Math.max(0, mGridCountX - 1); + if (gridCountY == 0) gridCountY++; + } else if ((gridCountY - 1) * gridCountX >= count && gridCountY >= gridCountX) { + gridCountY = Math.max(0, gridCountY - 1); + } else if ((gridCountX - 1) * gridCountY >= count) { + gridCountX = Math.max(0, gridCountX - 1); } - done = mGridCountX == oldCountX && mGridCountY == oldCountY; + done = gridCountX == oldCountX && gridCountY == oldCountY; } + out[0] = gridCountX; + out[1] = gridCountY; + } + + /** + * Sets up the grid size such that {@param count} items can fit in the grid. + */ + public void setupContentDimensions(int count) { + mAllocatedContentSize = count; + calculateGridSize(count, mGridCountX, mGridCountY, mMaxCountX, mMaxCountY, mMaxItemsPerPage, + sTmpArray); + mGridCountX = sTmpArray[0]; + mGridCountY = sTmpArray[1]; + // Update grid size for (int i = getPageCount() - 1; i >= 0; i--) { getPageAt(i).setGridSize(mGridCountX, mGridCountY); @@ -314,6 +331,8 @@ public class FolderPagedView extends PagedView { int position = 0; int newX, newY, rank; + FolderIconPreviewVerifier verifier = new FolderIconPreviewVerifier( + Launcher.getLauncher(getContext()).getDeviceProfile().inv); rank = 0; for (int i = 0; i < itemCount; i++) { View v = list.size() > i ? list.get(i) : null; @@ -346,7 +365,7 @@ public class FolderPagedView extends PagedView { currentPage.addViewToCellLayout( v, -1, mFolder.mLauncher.getViewIdForItem(info), lp, true); - if (rank < FolderIcon.NUM_ITEMS_IN_PREVIEW && v instanceof BubbleTextView) { + if (verifier.isItemInPreview(rank) && v instanceof BubbleTextView) { ((BubbleTextView) v).verifyHighRes(); } } @@ -400,12 +419,12 @@ public class FolderPagedView extends PagedView { public int findNearestArea(int pixelX, int pixelY) { int pageIndex = getNextPage(); CellLayout page = getPageAt(pageIndex); - page.findNearestArea(pixelX, pixelY, 1, 1, sTempPosArray); + page.findNearestArea(pixelX, pixelY, 1, 1, sTmpArray); if (mFolder.isLayoutRtl()) { - sTempPosArray[0] = page.getCountX() - sTempPosArray[0] - 1; + sTmpArray[0] = page.getCountX() - sTmpArray[0] - 1; } return Math.min(mAllocatedContentSize - 1, - pageIndex * mMaxItemsPerPage + sTempPosArray[1] * mGridCountX + sTempPosArray[0]); + pageIndex * mMaxItemsPerPage + sTmpArray[1] * mGridCountX + sTmpArray[0]); } public boolean isFull() { diff --git a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java index 297203ae2..9c8c2efdb 100644 --- a/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java +++ b/src/com/android/launcher3/folder/StackFolderIconLayoutRule.java @@ -87,6 +87,11 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { } @Override + public float getIconSize() { + return mBaselineIconSize; + } + + @Override public float scaleForItem(int index, int numItems) { // Scale is determined by the position of the icon in the preview. index = MAX_NUM_ITEMS_IN_PREVIEW - index - 1; @@ -98,10 +103,4 @@ public class StackFolderIconLayoutRule implements FolderIcon.PreviewLayoutRule { public boolean clipToBackground() { return false; } - - @Override - public List<View> getItemsToDisplay(Folder folder) { - List<View> items = folder.getItemsInReadingOrder(); - return items.subList(0, Math.min(items.size(), MAX_NUM_ITEMS_IN_PREVIEW)); - } } diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java index bb136f7a3..492d85373 100644 --- a/src/com/android/launcher3/graphics/DragPreviewProvider.java +++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java @@ -29,7 +29,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppWidgetHostView; import com.android.launcher3.R; import com.android.launcher3.Workspace; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; /** @@ -138,7 +138,7 @@ public class DragPreviewProvider { } public final void generateDragOutline(Canvas canvas) { - if (ProviderConfig.IS_DOGFOOD_BUILD && generatedDragOutline != null) { + if (FeatureFlags.IS_DOGFOOD_BUILD && generatedDragOutline != null) { throw new RuntimeException("Drag outline generated twice"); } diff --git a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java index c9873d9ea..b22182883 100644 --- a/src/com/android/launcher3/graphics/HolographicOutlineHelper.java +++ b/src/com/android/launcher3/graphics/HolographicOutlineHelper.java @@ -31,7 +31,7 @@ import android.util.SparseArray; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import java.nio.ByteBuffer; @@ -86,7 +86,7 @@ public class HolographicOutlineHelper { * bitmap. */ public void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas) { - if (ProviderConfig.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) { + if (FeatureFlags.IS_DOGFOOD_BUILD && srcDst.getConfig() != Bitmap.Config.ALPHA_8) { throw new RuntimeException("Outline blue is only supported on alpha bitmaps"); } diff --git a/src/com/android/launcher3/logging/DumpTargetWrapper.java b/src/com/android/launcher3/logging/DumpTargetWrapper.java new file mode 100644 index 000000000..2646a2242 --- /dev/null +++ b/src/com/android/launcher3/logging/DumpTargetWrapper.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.logging; + +import android.os.Process; +import android.text.TextUtils; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.model.nano.LauncherDumpProto; +import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType; +import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget; +import com.android.launcher3.model.nano.LauncherDumpProto.ItemType; +import com.android.launcher3.model.nano.LauncherDumpProto.UserType; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class can be used when proto definition doesn't support nesting. + */ +public class DumpTargetWrapper { + DumpTarget node; + ArrayList<DumpTargetWrapper> children; + + public DumpTargetWrapper() { + children = new ArrayList<>(); + } + + public DumpTargetWrapper(DumpTarget t) { + this(); + node = t; + } + + public DumpTargetWrapper(int containerType, int id) { + this(); + node = newContainerTarget(containerType, id); + } + + public DumpTargetWrapper(ItemInfo info) { + this(); + node = newItemTarget(info); + } + + public DumpTarget getDumpTarget() { + return node; + } + + public void add(DumpTargetWrapper child) { + children.add(child); + } + + public List<DumpTarget> getFlattenedList() { + ArrayList<DumpTarget> list = new ArrayList<>(); + list.add(node); + if (!children.isEmpty()) { + for(DumpTargetWrapper t: children) { + list.addAll(t.getFlattenedList()); + } + list.add(node); // add a delimiter empty object + } + return list; + } + public DumpTarget newItemTarget(ItemInfo info) { + DumpTarget dt = new DumpTarget(); + dt.type = DumpTarget.Type.ITEM; + + switch (info.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: + dt.itemType = ItemType.APP_ICON; + break; + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: + dt.itemType = ItemType.UNKNOWN_ITEMTYPE; + break; + case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: + dt.itemType = ItemType.WIDGET; + break; + case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: + dt.itemType = ItemType.SHORTCUT; + break; + } + return dt; + } + + public DumpTarget newContainerTarget(int type, int id) { + DumpTarget dt = new DumpTarget(); + dt.type = DumpTarget.Type.CONTAINER; + dt.containerType = type; + dt.pageId = id; + return dt; + } + + public static String getDumpTargetStr(DumpTarget t) { + if (t == null){ + return ""; + } + switch (t.type) { + case LauncherDumpProto.DumpTarget.Type.ITEM: + return getItemStr(t); + case LauncherDumpProto.DumpTarget.Type.CONTAINER: + String str = LoggerUtils.getFieldName(t.containerType, ContainerType.class); + if (t.containerType == ContainerType.WORKSPACE) { + str += " id=" + t.pageId; + } else if (t.containerType == ContainerType.FOLDER) { + str += " grid(" + t.gridX + "," + t.gridY+ ")"; + } + return str; + default: + return "UNKNOWN TARGET TYPE"; + } + } + + private static String getItemStr(DumpTarget t) { + String typeStr = LoggerUtils.getFieldName(t.itemType, ItemType.class); + if (!TextUtils.isEmpty(t.packageName)) { + typeStr += ", package=" + t.packageName; + } + if (!TextUtils.isEmpty(t.component)) { + typeStr += ", component=" + t.component; + } + return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY + + "), pageIdx=" + t.pageId + " user=" + t.userType; + } + + public DumpTarget writeToDumpTarget(ItemInfo info) { + node.component = info.getTargetComponent() == null? "": + info.getTargetComponent().flattenToString(); + node.packageName = info.getIntent() == null? "": info.getIntent().getPackage(); + node.gridX = info.cellX; + node.gridY = info.cellY; + node.spanX = info.spanX; + node.spanY = info.spanY; + node.userType = (info.user.equals(Process.myUserHandle()))? UserType.DEFAULT : UserType.WORK; + return node; + } +} diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java index ffb41b76b..4c83e9ac2 100644 --- a/src/com/android/launcher3/logging/FileLog.java +++ b/src/com/android/launcher3/logging/FileLog.java @@ -6,9 +6,8 @@ import android.os.Message; import android.util.Log; import android.util.Pair; -import com.android.launcher3.LauncherModel; import com.android.launcher3.Utilities; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import java.io.BufferedReader; import java.io.File; @@ -40,7 +39,7 @@ public final class FileLog { private static File sLogsDirectory = null; public static void setDir(File logsDir) { - if (ProviderConfig.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) { + if (FeatureFlags.IS_DOGFOOD_BUILD || Utilities.IS_DEBUG_DEVICE) { synchronized (DATE_FORMAT) { // If the target directory changes, stop any active thread. if (sHandler != null && !logsDir.equals(sLogsDirectory)) { @@ -77,7 +76,7 @@ public final class FileLog { } public static void print(String tag, String msg, Exception e) { - if (!ProviderConfig.IS_DOGFOOD_BUILD) { + if (!FeatureFlags.IS_DOGFOOD_BUILD) { return; } String out = String.format("%s %s %s", DATE_FORMAT.format(new Date()), tag, msg); @@ -103,7 +102,7 @@ public final class FileLog { * @param out if not null, all the persisted logs are copied to the writer. */ public static void flushAll(PrintWriter out) throws InterruptedException { - if (!ProviderConfig.IS_DOGFOOD_BUILD) { + if (!FeatureFlags.IS_DOGFOOD_BUILD) { return; } CountDownLatch latch = new CountDownLatch(1); @@ -136,7 +135,7 @@ public final class FileLog { @Override public boolean handleMessage(Message msg) { - if (sLogsDirectory == null || !ProviderConfig.IS_DOGFOOD_BUILD) { + if (sLogsDirectory == null || !FeatureFlags.IS_DOGFOOD_BUILD) { return true; } switch (msg.what) { diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java index c13e8b336..499fdc7d3 100644 --- a/src/com/android/launcher3/logging/LoggerUtils.java +++ b/src/com/android/launcher3/logging/LoggerUtils.java @@ -1,5 +1,21 @@ +/* + * Copyright (C) 2016 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.logging; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.SparseArray; import android.view.View; @@ -27,7 +43,7 @@ public class LoggerUtils { private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>(); private static final String UNKNOWN = "UNKNOWN"; - private static String getFieldName(int value, Class c) { + public static String getFieldName(int value, Class c) { SparseArray<String> cache; synchronized (sNameCache) { cache = sNameCache.get(c); @@ -68,8 +84,13 @@ public class LoggerUtils { case Target.Type.CONTROL: return getFieldName(t.controlType, ControlType.class); case Target.Type.CONTAINER: - return getFieldName(t.containerType, ContainerType.class) - + " id=" + t.pageIndex; + String str = getFieldName(t.containerType, ContainerType.class); + if (t.containerType == ContainerType.WORKSPACE) { + str += " id=" + t.pageIndex; + } else if (t.containerType == ContainerType.FOLDER) { + str += " grid(" + t.gridX + "," + t.gridY+ ")"; + } + return str; default: return "UNKNOWN TARGET TYPE"; } @@ -86,10 +107,8 @@ public class LoggerUtils { if (t.intentHash != 0) { typeStr += ", intentHash=" + t.intentHash; } - if (t.spanX != 0) { - typeStr += ", spanX=" + t.spanX; - } - return typeStr + ", grid=(" + t.gridX + "," + t.gridY + "), id=" + t.pageIndex; + return typeStr + ", grid(" + t.gridX + "," + t.gridY + "), span(" + t.spanX + "," + t.spanY + + "), pageIdx=" + t.pageIndex; } public static Target newItemTarget(View v) { diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java index 90e453117..04ca24741 100644 --- a/src/com/android/launcher3/logging/UserEventDispatcher.java +++ b/src/com/android/launcher3/logging/UserEventDispatcher.java @@ -29,8 +29,7 @@ import com.android.launcher3.DropTarget; import com.android.launcher3.ItemInfo; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.config.ProviderConfig; -import com.android.launcher3.userevent.nano.LauncherLogProto; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent; @@ -61,7 +60,7 @@ public class UserEventDispatcher { private static final String TAG = "UserEvent"; private static final boolean IS_VERBOSE = - ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT); + FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT); private static UserEventDispatcher sInstance; private static final Object LOCK = new Object(); diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 6b64087a2..930c854c3 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -24,19 +24,25 @@ import android.util.MutableInt; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.logging.DumpTargetWrapper; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.model.nano.LauncherDumpProto; +import com.android.launcher3.model.nano.LauncherDumpProto.ContainerType; +import com.android.launcher3.model.nano.LauncherDumpProto.DumpTarget; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.MultiHashMap; +import com.google.protobuf.nano.MessageNano; import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -102,21 +108,31 @@ public class BgDataModel { deepShortcutMap.clear(); } - // TODO: current dump is very cryptic and hard to understand. Make it more legible. - public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer, + String[] args) { + if (args.length > 0 && TextUtils.equals(args[0], "--proto")) { + dumpProto(prefix, fd, writer, args); + return; + } writer.println(prefix + "Data Model:"); + writer.print(prefix + " ---- workspace screens: "); for (int i = 0; i < workspaceScreens.size(); i++) { - writer.println(prefix + "\tIndex of workspaceScreens:" + workspaceScreens.get(i).toString()); + writer.print(" " + workspaceScreens.get(i).toString()); } + writer.println(); + writer.println(prefix + " ---- workspace items "); for (int i = 0; i < workspaceItems.size(); i++) { writer.println(prefix + '\t' + workspaceItems.get(i).toString()); } + writer.println(prefix + " ---- appwidget items "); for (int i = 0; i < appWidgets.size(); i++) { writer.println(prefix + '\t' + appWidgets.get(i).toString()); } + writer.println(prefix + " ---- folder items "); for (int i = 0; i< folders.size(); i++) { writer.println(prefix + '\t' + folders.valueAt(i).toString()); } + writer.println(prefix + " ---- items id map "); for (int i = 0; i< itemsIdMap.size(); i++) { writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString()); } @@ -133,6 +149,88 @@ public class BgDataModel { } } + private synchronized void dumpProto(String prefix, FileDescriptor fd, PrintWriter writer, + String[] args) { + + // Add top parent nodes. (L1) + DumpTargetWrapper hotseat = new DumpTargetWrapper(ContainerType.HOTSEAT, 0); + LongArrayMap<DumpTargetWrapper> workspaces = new LongArrayMap<>(); + for (int i = 0; i < workspaceScreens.size(); i++) { + workspaces.put(new Long(workspaceScreens.get(i)), + new DumpTargetWrapper(ContainerType.WORKSPACE, i)); + } + DumpTargetWrapper dtw; + // Add non leaf / non top nodes (L2) + for (int i = 0; i < folders.size(); i++) { + FolderInfo fInfo = folders.valueAt(i); + dtw = new DumpTargetWrapper(ContainerType.FOLDER, folders.size()); + dtw.writeToDumpTarget(fInfo); + for(ShortcutInfo sInfo: fInfo.contents) { + DumpTargetWrapper child = new DumpTargetWrapper(sInfo); + child.writeToDumpTarget(sInfo); + dtw.add(child); + } + if (fInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + hotseat.add(dtw); + } else if (fInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + workspaces.get(new Long(fInfo.screenId)).add(dtw); + } + } + // Add leaf nodes (L3): *Info + for (int i = 0; i < workspaceItems.size(); i++) { + ItemInfo info = workspaceItems.get(i); + if (info instanceof FolderInfo) { + continue; + } + dtw = new DumpTargetWrapper(info); + dtw.writeToDumpTarget(info); + if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + hotseat.add(dtw); + } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + workspaces.get(new Long(info.screenId)).add(dtw); + } + } + for (int i = 0; i < appWidgets.size(); i++) { + ItemInfo info = appWidgets.get(i); + dtw = new DumpTargetWrapper(info); + dtw.writeToDumpTarget(info); + if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + hotseat.add(dtw); + } else if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + workspaces.get(new Long(info.screenId)).add(dtw); + } + } + + + // Traverse target wrapper + ArrayList<DumpTarget> targetList = new ArrayList<>(); + targetList.addAll(hotseat.getFlattenedList()); + for (int i = 0; i < workspaces.size(); i++) { + targetList.addAll(workspaces.valueAt(i).getFlattenedList()); + } + + if (args.length > 1 && TextUtils.equals(args[1], "--debug")) { + for (int i = 0; i < targetList.size(); i++) { + writer.println(prefix + DumpTargetWrapper.getDumpTargetStr(targetList.get(i))); + } + return; + } else { + LauncherDumpProto.LauncherImpression proto = new LauncherDumpProto.LauncherImpression(); + proto.targets = new DumpTarget[targetList.size()]; + for (int i = 0; i < targetList.size(); i++) { + proto.targets[i] = targetList.get(i); + } + FileOutputStream fos = new FileOutputStream(fd); + try { + + fos.write(MessageNano.toByteArray(proto)); + Log.d(TAG, MessageNano.toByteArray(proto).length + "Bytes"); + } catch (IOException e) { + Log.e(TAG, "Exception writing dumpsys --proto", e); + } + } + } + public synchronized void removeItem(Context context, ItemInfo... items) { removeItem(context, Arrays.asList(items)); } @@ -142,7 +240,7 @@ public class BgDataModel { switch (item.itemType) { case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: folders.remove(item.id); - if (ProviderConfig.IS_DOGFOOD_BUILD) { + if (FeatureFlags.IS_DOGFOOD_BUILD) { for (ItemInfo info : itemsIdMap) { if (info.container == item.id) { // We are deleting a folder which still contains items that diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index 4931dca14..032ed780d 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -34,7 +34,7 @@ import com.android.launcher3.LauncherSettings.Settings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.LooperExecuter; +import com.android.launcher3.util.LooperExecutor; import java.util.ArrayList; import java.util.Arrays; @@ -55,7 +55,7 @@ public class ModelWriter { public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) { mContext = context; mBgDataModel = dataModel; - mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper()); + mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper()); mHasVerticalHotseat = hasVerticalHotseat; } diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 95c54f767..d2adbde23 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -3,9 +3,7 @@ package com.android.launcher3.model; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.os.Process; import android.os.UserHandle; import android.util.Log; @@ -19,8 +17,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; -import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.Preconditions; @@ -79,7 +76,7 @@ public class WidgetsModel { } setWidgetsAndShortcuts(widgetsAndShortcuts, context); } catch (Exception e) { - if (!ProviderConfig.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) { + if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) { // the returned value may be incomplete and will not be refreshed until the next // time Launcher starts. // TODO: after figuring out a repro step, introduce a dirty bit to check when diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java index b839d99ff..eeece881b 100644 --- a/src/com/android/launcher3/popup/PopupItemView.java +++ b/src/com/android/launcher3/popup/PopupItemView.java @@ -36,7 +36,7 @@ import android.widget.FrameLayout; import com.android.launcher3.LogAccelerateInterpolator; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.util.PillRevealOutlineProvider; +import com.android.launcher3.anim.PillRevealOutlineProvider; /** * An abstract {@link FrameLayout} that supports animating an item's content diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java index b0482f8b2..3e4cd0192 100644 --- a/src/com/android/launcher3/provider/ImportDataTask.java +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -37,6 +37,7 @@ import com.android.launcher3.DefaultLayoutParser; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.LauncherSettings.Settings; @@ -46,7 +47,6 @@ import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.util.LongArrayMap; @@ -112,7 +112,7 @@ public class ImportDataTask { screenOps.add(ContentProviderOperation.newInsert( LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build()); } - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, screenOps); + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, screenOps); importWorkspaceItems(allScreens.get(0), screenIdMap); GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize); @@ -289,7 +289,7 @@ public class ImportDataTask { } if (insertOperations.size() >= BATCH_INSERT_SIZE) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); insertOperations.clear(); } @@ -300,7 +300,7 @@ public class ImportDataTask { throw new Exception("Insufficient data"); } if (!insertOperations.isEmpty()) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); insertOperations.clear(); } @@ -319,7 +319,7 @@ public class ImportDataTask { mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1; if (!insertOperations.isEmpty()) { - mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, insertOperations); } } diff --git a/src/com/android/launcher3/util/LooperExecuter.java b/src/com/android/launcher3/util/LooperExecutor.java index 4db999bce..5b7c20bbf 100644 --- a/src/com/android/launcher3/util/LooperExecuter.java +++ b/src/com/android/launcher3/util/LooperExecutor.java @@ -25,11 +25,11 @@ import java.util.concurrent.TimeUnit; /** * Extension of {@link AbstractExecutorService} which executed on a provided looper. */ -public class LooperExecuter extends AbstractExecutorService { +public class LooperExecutor extends AbstractExecutorService { private final Handler mHandler; - public LooperExecuter(Looper looper) { + public LooperExecutor(Looper looper) { mHandler = new Handler(looper); } diff --git a/src/com/android/launcher3/util/LooperIdleLock.java b/src/com/android/launcher3/util/LooperIdleLock.java new file mode 100644 index 000000000..35cac14e3 --- /dev/null +++ b/src/com/android/launcher3/util/LooperIdleLock.java @@ -0,0 +1,71 @@ +/* + * 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.util; + +import android.os.Looper; +import android.os.MessageQueue; + +import com.android.launcher3.Utilities; + +/** + * Utility class to block execution until the UI looper is idle. + */ +public class LooperIdleLock implements MessageQueue.IdleHandler, Runnable { + + private final Object mLock; + + private boolean mIsLocked; + + public LooperIdleLock(Object lock, Looper looper) { + mLock = lock; + mIsLocked = true; + if (Utilities.ATLEAST_MARSHMALLOW) { + looper.getQueue().addIdleHandler(this); + } else { + // Looper.myQueue() only gives the current queue. Move the execution to the UI thread + // so that the IdleHandler is attached to the correct message queue. + new LooperExecutor(looper).execute(this); + } + } + + @Override + public void run() { + Looper.myQueue().addIdleHandler(this); + } + + @Override + public boolean queueIdle() { + synchronized (mLock) { + mIsLocked = false; + mLock.notify(); + } + return false; + } + + public boolean awaitLocked(long ms) { + if (mIsLocked) { + try { + // Just in case mFlushingWorkerThread changes but we aren't woken up, + // wait no longer than 1sec at a time + mLock.wait(ms); + } catch (InterruptedException ex) { + // Ignore + } + } + return mIsLocked; + } +} diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java index 89353e110..7ab0d3103 100644 --- a/src/com/android/launcher3/util/Preconditions.java +++ b/src/com/android/launcher3/util/Preconditions.java @@ -19,7 +19,7 @@ package com.android.launcher3.util; import android.os.Looper; import com.android.launcher3.LauncherModel; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; /** * A set of utility methods for thread verification. @@ -27,25 +27,25 @@ import com.android.launcher3.config.ProviderConfig; public class Preconditions { public static void assertNotNull(Object o) { - if (ProviderConfig.IS_DOGFOOD_BUILD && o == null) { + if (FeatureFlags.IS_DOGFOOD_BUILD && o == null) { throw new IllegalStateException(); } } public static void assertWorkerThread() { - if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(LauncherModel.getWorkerLooper())) { throw new IllegalStateException(); } } public static void assertUIThread() { - if (ProviderConfig.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) { throw new IllegalStateException(); } } public static void assertNonUiThread() { - if (ProviderConfig.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) { + if (FeatureFlags.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) { throw new IllegalStateException(); } } diff --git a/src/com/android/launcher3/util/SQLiteCacheHelper.java b/src/com/android/launcher3/util/SQLiteCacheHelper.java index 1ff6293a0..5344416ea 100644 --- a/src/com/android/launcher3/util/SQLiteCacheHelper.java +++ b/src/com/android/launcher3/util/SQLiteCacheHelper.java @@ -10,7 +10,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import com.android.launcher3.Utilities; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.config.FeatureFlags; /** * An extension of {@link SQLiteOpenHelper} with utility methods for a single table cache DB. @@ -19,7 +19,7 @@ import com.android.launcher3.config.ProviderConfig; public abstract class SQLiteCacheHelper { private static final String TAG = "SQLiteCacheHelper"; - private static final boolean NO_ICON_CACHE = ProviderConfig.IS_DOGFOOD_BUILD && + private static final boolean NO_ICON_CACHE = FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.MEMORY_ONLY_ICON_CACHE); private final String mTableName; diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java index 9bd288244..4cb6ca831 100644 --- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java +++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java @@ -16,12 +16,10 @@ package com.android.launcher3.util; -import android.util.Log; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.ViewTreeObserver.OnDrawListener; -import com.android.launcher3.DeferredHandler; import com.android.launcher3.Launcher; import java.util.ArrayList; @@ -34,7 +32,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, OnAttachStateChangeListener { private final ArrayList<Runnable> mTasks = new ArrayList<>(); - private final DeferredHandler mHandler; + private final Executor mExecutor; private Launcher mLauncher; private View mAttachedView; @@ -43,8 +41,8 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, private boolean mLoadAnimationCompleted; private boolean mFirstDrawCompleted; - public ViewOnDrawExecutor(DeferredHandler handler) { - mHandler = handler; + public ViewOnDrawExecutor(Executor executor) { + mExecutor = executor; } public void attachTo(Launcher launcher) { @@ -92,7 +90,7 @@ public class ViewOnDrawExecutor implements Executor, OnDrawListener, Runnable, // Post the pending tasks after both onDraw and onLoadAnimationCompleted have been called. if (mLoadAnimationCompleted && mFirstDrawCompleted && !mCompleted) { for (final Runnable r : mTasks) { - mHandler.post(r); + mExecutor.execute(r); } markCompleted(); } diff --git a/src_config/com/android/launcher3/config/ProviderConfig.java b/src_config/com/android/launcher3/BuildConfig.java index 491fa657a..4df75a1a1 100644 --- a/src_config/com/android/launcher3/config/ProviderConfig.java +++ b/src_config/com/android/launcher3/BuildConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * 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. @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.launcher3.config; +package com.android.launcher3; -public class ProviderConfig { - - public static final String AUTHORITY = "com.android.launcher3.settings".intern(); - - public static final boolean IS_DOGFOOD_BUILD = true; +/** + * Config file used by Make. This file is automatically generated when using gradle. + */ +public class BuildConfig { + public static final String APPLICATION_ID = "com.android.launcher3"; } diff --git a/src_config/com/android/launcher3/config/FeatureFlags.java b/src_flags/com/android/launcher3/config/FeatureFlags.java index 80eebece2..87e987162 100644 --- a/src_config/com/android/launcher3/config/FeatureFlags.java +++ b/src_flags/com/android/launcher3/config/FeatureFlags.java @@ -20,6 +20,9 @@ package com.android.launcher3.config; * Defines a set of flags used to control various launcher behaviors */ public final class FeatureFlags { + + public static final boolean IS_DOGFOOD_BUILD = true; + private FeatureFlags() {} // Custom flags go below this @@ -28,7 +31,7 @@ public final class FeatureFlags { public static boolean LAUNCHER3_USE_SYSTEM_DRAG_DRIVER = true; public static boolean LAUNCHER3_DISABLE_PINCH_TO_OVERVIEW = false; public static boolean LAUNCHER3_ALL_APPS_PULL_UP = true; - public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = false; + public static boolean LAUNCHER3_NEW_FOLDER_ANIMATION = true; // When enabled allows to use any point on the fast scrollbar to start dragging. public static boolean LAUNCHER3_DIRECT_SCROLL = true; // When enabled while all-apps open, the soft input will be set to adjust resize . diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java index d0ba9074c..883be5aa3 100644 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -10,9 +10,9 @@ import android.net.Uri; import android.util.Pair; import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.Provider; @@ -178,6 +178,6 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build()); } - getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops); + getMockContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops); } } diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index b9944db98..13e09869f 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -16,7 +16,6 @@ import android.test.ProviderTestCase2; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppFilter; import com.android.launcher3.AppInfo; -import com.android.launcher3.DeferredHandler; import com.android.launcher3.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; @@ -24,7 +23,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.BaseModelUpdateTask; import com.android.launcher3.LauncherModel.Callbacks; -import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.LauncherProvider; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Provider; import com.android.launcher3.util.TestLauncherProvider; @@ -36,6 +35,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.HashMap; import java.util.List; +import java.util.concurrent.Executor; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.atLeast; @@ -64,7 +64,7 @@ public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherP public Callbacks callbacks; public BaseModelUpdateTaskTestCase() { - super(TestLauncherProvider.class, ProviderConfig.AUTHORITY); + super(TestLauncherProvider.class, LauncherProvider.AUTHORITY); } @Override @@ -102,14 +102,14 @@ public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherP f.setAccessible(true); f.set(task, mockModel); - DeferredHandler mockHandler = mock(DeferredHandler.class); - f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler"); + Executor mockExecutor = mock(Executor.class); + f = BaseModelUpdateTask.class.getDeclaredField("mUiExecutor"); f.setAccessible(true); - f.set(task, mockHandler); + f.set(task, mockExecutor); task.execute(appState, bgDataModel, allAppsList); ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); - verify(mockHandler, atLeast(0)).post(captor.capture()); + verify(mockExecutor, atLeast(0)).execute(captor.capture()); return captor.getAllValues(); } diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java index fc7fe48fd..fd62d3644 100644 --- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java +++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java @@ -1,7 +1,6 @@ package com.android.launcher3.model; import android.content.ContentValues; -import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Point; @@ -10,9 +9,9 @@ import android.test.suitebuilder.annotation.MediumTest; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask; import com.android.launcher3.util.TestLauncherProvider; @@ -40,7 +39,7 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro private InvariantDeviceProfile mIdp; public GridSizeMigrationTaskTest() { - super(TestLauncherProvider.class, ProviderConfig.AUTHORITY); + super(TestLauncherProvider.class, LauncherProvider.AUTHORITY); } @Override diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index df2b66285..97f7b505a 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -39,13 +39,12 @@ import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.ui.LauncherInstrumentationTestCase; import com.android.launcher3.util.ContentWriter; -import com.android.launcher3.util.LooperExecuter; +import com.android.launcher3.util.LooperExecutor; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; import java.util.Set; import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** @@ -340,7 +339,7 @@ public class BindWidgetTest extends LauncherInstrumentationTestCase { * Blocks the current thread until all the jobs in the main worker thread are complete. */ private void waitUntilLoaderIdle() throws Exception { - new LooperExecuter(LauncherModel.getWorkerLooper()) + new LooperExecutor(LauncherModel.getWorkerLooper()) .submit(new Runnable() { @Override public void run() { } |