From fca20151b06dbad1a121cd471947ce4f3ca3132b Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 25 Nov 2019 11:14:38 -0800 Subject: [PATCH 01/30] Removing custom activity dump logic Change-Id: I37c339be205a4e35227986544d36ca03b1ae4ba4 --- .../launcher3/uioverrides/ApiWrapper.java | 33 ------------------- src/com/android/launcher3/BaseActivity.java | 9 ----- .../launcher3/uioverrides/ApiWrapper.java | 7 ---- 3 files changed, 49 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java index 965b5f0ad7..1e03b05b5e 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java @@ -16,46 +16,13 @@ package com.android.launcher3.uioverrides; -import android.app.Activity; import android.app.Person; import android.content.pm.ShortcutInfo; -import android.util.Base64; import com.android.launcher3.Utilities; -import com.android.systemui.shared.system.ActivityCompat; - -import java.io.ByteArrayOutputStream; -import java.io.PrintWriter; -import java.util.zip.Deflater; public class ApiWrapper { - public static boolean dumpActivity(Activity activity, PrintWriter writer) { - if (!Utilities.IS_DEBUG_DEVICE) { - return false; - } - ByteArrayOutputStream out = new ByteArrayOutputStream(); - if (!(new ActivityCompat(activity).encodeViewHierarchy(out))) { - return false; - } - - Deflater deflater = new Deflater(); - deflater.setInput(out.toByteArray()); - deflater.finish(); - - out.reset(); - byte[] buffer = new byte[1024]; - while (!deflater.finished()) { - int count = deflater.deflate(buffer); // returns the generated code... index - out.write(buffer, 0, count); - } - - writer.println("--encoded-view-dump-v0--"); - writer.println(Base64.encodeToString( - out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); - return true; - } - public static Person[] getPersons(ShortcutInfo si) { Person[] persons = si.getPersons(); return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 382bfdf513..f319ae1812 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -35,13 +35,11 @@ import com.android.launcher3.logging.StatsLogUtils; import com.android.launcher3.logging.StatsLogUtils.LogStateProvider; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate; -import com.android.launcher3.uioverrides.ApiWrapper; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.ViewCache; import com.android.launcher3.views.ActivityContext; -import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.util.ArrayList; @@ -305,13 +303,6 @@ public abstract class BaseActivity extends Activity void onMultiWindowModeChanged(boolean isInMultiWindowMode); } - @Override - public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { - if (!ApiWrapper.dumpActivity(this, writer)) { - super.dump(prefix, fd, writer, args); - } - } - protected void dumpMisc(String prefix, PrintWriter writer) { writer.println(prefix + "deviceProfile isTransposed=" + getDeviceProfile().isVerticalBarLayout()); diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java index 5407ea3182..6fd147a055 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java @@ -16,20 +16,13 @@ package com.android.launcher3.uioverrides; -import android.app.Activity; import android.app.Person; import android.content.pm.ShortcutInfo; import com.android.launcher3.Utilities; -import java.io.PrintWriter; - public class ApiWrapper { - public static boolean dumpActivity(Activity activity, PrintWriter writer) { - return false; - } - public static Person[] getPersons(ShortcutInfo si) { return Utilities.EMPTY_PERSON_ARRAY; } From 275e841c7b82c2c4fc2c69a2afd7dc15ac5f23cd Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 4 Dec 2019 07:09:23 -0800 Subject: [PATCH 02/30] Import translations. DO NOT MERGE Change-Id: Ia7ef20643a0f85e3805cdcbc4a43aae07c433bd2 Auto-generated-cl: translation import --- res/values-mr/strings.xml | 4 ++-- res/values-or/strings.xml | 2 +- res/values-pl/strings.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index 3ae8f1ad15..40a3a1e4d3 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -47,7 +47,7 @@ "आवडीच्या ट्रे मध्ये आणखी जागा नाही" "अ‍ॅप्स सूची" "वैयक्तिक अ‍ॅप्स सूची" - "कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची" + "कामाच्या ठिकाणी वापरली जाणाऱ्या ॲप्सची सूची" "होम" "काढा" "अनइंस्टॉल करा" @@ -93,7 +93,7 @@ "सेटिंग्ज बदला" "सूचना बिंदू दाखवा" "होम स्क्रीनवर आयकन जोडा" - "नवीन अॅप्ससाठी" + "नवीन ॲप्ससाठी" "अज्ञात" "काढा" "शोधा" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index ab42923019..0b3fb99e58 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -86,7 +86,7 @@ "ହୋମ୍‌ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ" "ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ" "ବିଜ୍ଞପ୍ତି ଡଟ୍ସ" - "ଚାଲୁ କରନ୍ତୁ" + "ଚାଲୁ" "ବନ୍ଦ କରନ୍ତୁ" "ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍‌ ଆବଶ୍ୟକ ଅଟେ" "ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, %1$s ପାଇଁ ଆପ୍‌ ବିଜ୍ଞପ୍ତି ଅନ୍‌ କରନ୍ତୁ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 68859605fb..1cf09051ae 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -130,7 +130,7 @@ "Szerokość i wysokość widżetu zmieniła się na %1$s x %2$s" "Skróty" "Skróty i powiadomienia" - "Odrzuć" + "Zamknij" "Powiadomienie odrzucone" "Osobiste" "Praca" From 6a38180482352b450fdd233c440c8e4b00ffd0f3 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Wed, 4 Dec 2019 07:12:46 -0800 Subject: [PATCH 03/30] Import translations. DO NOT MERGE Change-Id: If3f89987d7cbcad1ab77400785235af4e17debcf Auto-generated-cl: translation import --- res/values-mr/strings.xml | 4 ++-- res/values-or/strings.xml | 2 +- res/values-pl/strings.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml index f71bbd9024..69eb5ee304 100644 --- a/res/values-mr/strings.xml +++ b/res/values-mr/strings.xml @@ -46,7 +46,7 @@ "आवडीच्या ट्रे मध्ये आणखी जागा नाही" "अ‍ॅप्स सूची" "वैयक्तिक अ‍ॅप्स सूची" - "कामाच्या ठिकाणी वापरली जाणाऱ्या अॅप्सची सूची" + "कामाच्या ठिकाणी वापरली जाणाऱ्या ॲप्सची सूची" "होम" "काढा" "अनइंस्टॉल करा" @@ -92,7 +92,7 @@ "सेटिंग्ज बदला" "सूचना बिंदू दाखवा" "होम स्क्रीनवर आयकन जोडा" - "नवीन अॅप्ससाठी" + "नवीन ॲप्ससाठी" "अज्ञात" "काढा" "शोधा" diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml index 37bd2d5f8b..2479d1bdf4 100644 --- a/res/values-or/strings.xml +++ b/res/values-or/strings.xml @@ -85,7 +85,7 @@ "ହୋମ୍‌ ସ୍କ୍ରୀନ୍ ବୁଲାଇବା ଅନୁମତି ଦିଅନ୍ତୁ" "ଯେତେବେଳେ ଫୋନକୁ ବୁଲାଯାଇଥାଏ" "ବିଜ୍ଞପ୍ତି ଡଟ୍ସ" - "ଚାଲୁ କରନ୍ତୁ" + "ଚାଲୁ" "ବନ୍ଦ କରନ୍ତୁ" "ବିଜ୍ଞପ୍ତି ଆକ୍ସେସ୍‌ ଆବଶ୍ୟକ ଅଟେ" "ବିଜ୍ଞପ୍ତି ବିନ୍ଦୁ ଦେଖାଇବାକୁ, %1$s ପାଇଁ ଆପ୍‌ ବିଜ୍ଞପ୍ତି ଅନ୍‌ କରନ୍ତୁ" diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 5d9b50e4f8..48c8baff0d 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -129,7 +129,7 @@ "Szerokość i wysokość widżetu zmieniła się na %1$s x %2$s" "Skróty" "Skróty i powiadomienia" - "Odrzuć" + "Zamknij" "Powiadomienie odrzucone" "Osobiste" "Praca" From 3a39a25d1c93d0c078c5048585247143b98cd701 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Mon, 2 Dec 2019 18:31:43 -0800 Subject: [PATCH 04/30] Ignore predicted items when items are drag over them Bug:145565886 Test:Manual Change-Id: I727975234d36ef2cba0d58ba1df6886e09157ce6 --- .../com/android/launcher3/HotseatPredictionController.java | 3 ++- .../com/android/launcher3/uioverrides/PredictedAppIcon.java | 2 ++ src/com/android/launcher3/Workspace.java | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java index 78cf44e1b0..0254340795 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -135,7 +135,7 @@ public class HotseatPredictionController implements DragController.DragListener, } WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++); - if (isPredictedIcon(child)) { + if (isPredictedIcon(child) && child.isEnabled()) { PredictedAppIcon icon = (PredictedAppIcon) child; icon.applyFromWorkspaceItem(predictedItem); icon.finishBinding(); @@ -310,6 +310,7 @@ public class HotseatPredictionController implements DragController.DragListener, int rank = ((WorkspaceItemInfo) icon.getTag()).rank; outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing( mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon)); + icon.setEnabled(false); icon.animate().scaleY(0).scaleX(0).setListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java index f97d6daeda..1dcbffb38f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java @@ -98,6 +98,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView { applyFromWorkspaceItem(info); setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE); mIsPinned = true; + ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; invalidate(); } @@ -112,6 +113,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView { } return true; }); + ((CellLayout.LayoutParams) getLayoutParams()).canReorder = false; setTextVisibility(false); verifyHighRes(); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index d445bc92e6..816d71008b 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -1641,7 +1641,9 @@ public class Workspace extends PagedView return false; } - boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo); + boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) dropOverView.getTag()).container + != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION); boolean willBecomeShortcut = (info.itemType == ITEM_TYPE_APPLICATION || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT || From 02c794578c380745341956f1441d4c12798ae13f Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Wed, 4 Dec 2019 14:20:27 -0800 Subject: [PATCH 05/30] Persist predicted items when dragged to workspace Bug: 143787549 Test: Manual Change-Id: Ieff0ede4072a2a9a87a62b06ad846eedfe97583f --- src/com/android/launcher3/model/ModelWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java index c69ace946c..bdf3a6918d 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -103,7 +103,7 @@ public class ModelWriter { */ public void addOrMoveItemInDatabase(ItemInfo item, int container, int screenId, int cellX, int cellY) { - if (item.container == ItemInfo.NO_ID) { + if (item.id == ItemInfo.NO_ID) { // From all apps addItemToDatabase(item, container, screenId, cellX, cellY); } else { From 6af66b01c786616a2c04595e1f1c9c92b3e21a86 Mon Sep 17 00:00:00 2001 From: Jeongik Cha Date: Tue, 9 Jul 2019 10:08:39 +0900 Subject: [PATCH 06/30] Move the app to /system_ext As mentioned in go/partitions-in-r, we plan to forbid hidden API in product partition. So this app should be moved to /system_ext because it uses hidden API. It is not permanent, so they can be moved back to /product due to needs if there isn't hidden usage anymore. Bug: 137908189 Test: m Test: check whether device boots successfully Change-Id: Ida940a2e1bb832cf5c3d147297e7ed65fa6921d5 --- Android.mk | 10 +++++----- CleanSpec.mk | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Android.mk b/Android.mk index 5def65f50c..beb13c58a2 100644 --- a/Android.mk +++ b/Android.mk @@ -94,7 +94,7 @@ LOCAL_SDK_VERSION := current LOCAL_MIN_SDK_VERSION := 21 LOCAL_PACKAGE_NAME := Launcher3 LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -125,7 +125,7 @@ LOCAL_SDK_VERSION := current LOCAL_MIN_SDK_VERSION := 21 LOCAL_PACKAGE_NAME := Launcher3Go LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -191,7 +191,7 @@ else endif LOCAL_PACKAGE_NAME := Launcher3QuickStep LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -241,7 +241,7 @@ LOCAL_PROGUARD_ENABLED := full LOCAL_PACKAGE_NAME := Launcher3QuickStepGo LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3QuickStep Launcher3GoIconRecents LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 @@ -287,7 +287,7 @@ LOCAL_PROGUARD_ENABLED := full LOCAL_PACKAGE_NAME := Launcher3GoIconRecents LOCAL_PRIVILEGED_MODULE := true -LOCAL_PRODUCT_MODULE := true +LOCAL_SYSTEM_EXT_MODULE := true LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3 Launcher3Go Launcher3QuickStep LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3 diff --git a/CleanSpec.mk b/CleanSpec.mk index b2c5266fa2..f7bda94333 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -53,6 +53,16 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/Launcher2_intermediates) $(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/app/Launcher2.apk) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3Go) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStep) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3QuickStepGo) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/product/priv-app/Launcher3GoIconRecents) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3Go) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStep) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3QuickStepGo) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/system/product/priv-app/Launcher3GoIconRecents) # ************************************************ # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST # ************************************************ From 8b7483810ac4aca3e40b0467a9282fa8250e9ce3 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Thu, 5 Dec 2019 10:54:53 -0800 Subject: [PATCH 07/30] migrate from deprecated fragment api to latest Bug: 145475911 Test: Settings -> App & Notifications -> Default Apps -> Gear Button next to Home app -> Verify everything works Change-Id: Icad09230e00991da91dd0b3f870ab46edc04d1a3 --- .../launcher3/settings/SettingsActivity.java | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index 18b6094fb0..f4c285265d 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -21,9 +21,6 @@ import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERE import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; -import android.app.Activity; -import android.app.DialogFragment; -import android.app.Fragment; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; @@ -32,6 +29,18 @@ import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.preference.Preference; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback; +import androidx.preference.PreferenceFragmentCompat.OnPreferenceStartScreenCallback; +import androidx.preference.PreferenceGroup.PreferencePositionCallback; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + import com.android.launcher3.LauncherFiles; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -40,18 +49,10 @@ import com.android.launcher3.graphics.GridOptionsProvider; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; import com.android.launcher3.util.SecureSettingsObserver; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragment; -import androidx.preference.PreferenceFragment.OnPreferenceStartFragmentCallback; -import androidx.preference.PreferenceFragment.OnPreferenceStartScreenCallback; -import androidx.preference.PreferenceGroup.PreferencePositionCallback; -import androidx.preference.PreferenceScreen; -import androidx.recyclerview.widget.RecyclerView; - /** * Settings activity for Launcher. Currently implements the following setting: Allow rotation */ -public class SettingsActivity extends Activity +public class SettingsActivity extends FragmentActivity implements OnPreferenceStartFragmentCallback, OnPreferenceStartScreenCallback, SharedPreferences.OnSharedPreferenceChangeListener{ @@ -80,15 +81,16 @@ public class SettingsActivity extends Activity args.putString(EXTRA_FRAGMENT_ARG_KEY, prefKey); } - Fragment f = Fragment.instantiate( - this, getString(R.string.settings_fragment_name), args); + final FragmentManager fm = getSupportFragmentManager(); + final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), + getString(R.string.settings_fragment_name)); + f.setArguments(args); // Display the fragment as the main content. - getFragmentManager().beginTransaction() - .replace(android.R.id.content, f) - .commit(); + fm.beginTransaction().replace(android.R.id.content, f).commit(); } Utilities.getPrefs(getApplicationContext()).registerOnSharedPreferenceChangeListener(this); } + @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (GRID_OPTIONS_PREFERENCE_KEY.equals(key)) { @@ -112,41 +114,39 @@ public class SettingsActivity extends Activity } private boolean startFragment(String fragment, Bundle args, String key) { - if (Utilities.ATLEAST_P && getFragmentManager().isStateSaved()) { + if (Utilities.ATLEAST_P && getSupportFragmentManager().isStateSaved()) { // Sometimes onClick can come after onPause because of being posted on the handler. // Skip starting new fragments in that case. return false; } - Fragment f = Fragment.instantiate(this, fragment, args); + final FragmentManager fm = getSupportFragmentManager(); + final Fragment f = fm.getFragmentFactory().instantiate(getClassLoader(), fragment); + f.setArguments(args); if (f instanceof DialogFragment) { - ((DialogFragment) f).show(getFragmentManager(), key); + ((DialogFragment) f).show(getSupportFragmentManager(), key); } else { - getFragmentManager() - .beginTransaction() - .replace(android.R.id.content, f) - .addToBackStack(key) - .commit(); + fm.beginTransaction().replace(android.R.id.content, f).addToBackStack(key).commit(); } return true; } @Override public boolean onPreferenceStartFragment( - PreferenceFragment preferenceFragment, Preference pref) { + PreferenceFragmentCompat preferenceFragment, Preference pref) { return startFragment(pref.getFragment(), pref.getExtras(), pref.getKey()); } @Override - public boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref) { + public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) { Bundle args = new Bundle(); - args.putString(PreferenceFragment.ARG_PREFERENCE_ROOT, pref.getKey()); + args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey()); return startFragment(getString(R.string.settings_fragment_name), args, pref.getKey()); } /** * This fragment shows the launcher preferences. */ - public static class LauncherSettingsFragment extends PreferenceFragment { + public static class LauncherSettingsFragment extends PreferenceFragmentCompat { private SecureSettingsObserver mNotificationDotsObserver; From 865dc9b3b17084c73cf07868b115cc7710b7dfc1 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Thu, 5 Dec 2019 11:00:11 -0800 Subject: [PATCH 08/30] perform accessbility focus when the recyclerview doesn't gain focus after fragment replacement in SettingsActivity Bug: 145475911 Test: Settings -> App & Notifications -> Default Apps -> Enable Talkback -> Gear Button next to Home app -> About -> Verify Talkback reads the first item in the page Change-Id: I100e646ad2aba0081b48b86ec408ce8e76a80713 --- .../launcher3/settings/SettingsActivity.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java index f4c285265d..12085c8b46 100644 --- a/src/com/android/launcher3/settings/SettingsActivity.java +++ b/src/com/android/launcher3/settings/SettingsActivity.java @@ -16,6 +16,8 @@ package com.android.launcher3.settings; +import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS; + import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY; import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY; import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue; @@ -29,6 +31,7 @@ import android.os.Bundle; import android.provider.Settings; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; @@ -248,6 +251,8 @@ public class SettingsActivity extends FragmentActivity if (highlighter != null) { getView().postDelayed(highlighter, DELAY_HIGHLIGHT_DURATION_MILLIS); mPreferenceHighlighted = true; + } else { + requestAccessibilityFocus(getListView()); } } } @@ -268,6 +273,15 @@ public class SettingsActivity extends FragmentActivity return position >= 0 ? new PreferenceHighlighter(list, position) : null; } + private void requestAccessibilityFocus(@NonNull final RecyclerView rv) { + rv.post(() -> { + if (!rv.hasFocus() && rv.getChildCount() > 0) { + rv.getChildAt(0) + .performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null); + } + }); + } + @Override public void onDestroy() { if (mNotificationDotsObserver != null) { From eacbdd3cde3c32530c057ab878753b0b1489d475 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Mon, 2 Dec 2019 17:13:37 -0800 Subject: [PATCH 09/30] allow smart folder to support more than 2 items Bug: 144975153 Test: Manual Change-Id: Ib5ff34863312d8b8da7615371e353bb697261790 --- src/com/android/launcher3/folder/Folder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 33da582293..f59a192f11 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -417,7 +417,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo * Show suggested folder title. */ public void showSuggestedTitle(String[] suggestName) { - if (FeatureFlags.FOLDER_NAME_SUGGEST.get() && mInfo.contents.size() == 2) { + if (FeatureFlags.FOLDER_NAME_SUGGEST.get() + && TextUtils.isEmpty(mFolderName.getText().toString())) { if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) { mFolderName.setHint(suggestName[0]); mFolderName.setText(suggestName[0]); From 8a50a6d8589a155e4ee49e321b52c261da955d53 Mon Sep 17 00:00:00 2001 From: Tony Wickham Date: Wed, 27 Nov 2019 16:54:01 -0600 Subject: [PATCH 10/30] Send home intent after reaching 3P launcher on swipe up Also keep the 3P launcher's alpha at 0 during the gesture, and don't send the home intent if user touches during the transition. Bug: 139682945 Change-Id: Ie758f0b337bb173b34f5585ec1915b7ea1145094 --- .../android/quickstep/BaseSwipeUpHandler.java | 32 +++++++------ .../quickstep/FallbackSwipeHandler.java | 45 ++++++++++++++++--- .../tapl/LauncherInstrumentation.java | 1 + 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java index b14da5c8d8..630dd70c03 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java @@ -430,20 +430,6 @@ public abstract class BaseSwipeUpHandler= end) { - return 0f; - } - return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); - } - @Override public void onUpdate(RectF currentRect, float progress) { homeAnim.setPlayFraction(progress); @@ -484,6 +470,24 @@ public abstract class BaseSwipeUpHandler= end) { + return 0f; + } + return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); + } + public interface Factory { BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs, diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java index c939de88f5..034f15899f 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java @@ -15,6 +15,8 @@ */ package com.android.quickstep; +import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; +import static com.android.launcher3.anim.Interpolators.ACCEL_2; import static com.android.quickstep.GestureState.GestureEndTarget.HOME; import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK; import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK; @@ -34,6 +36,7 @@ import android.graphics.PointF; import android.graphics.RectF; import android.os.Bundle; import android.util.ArrayMap; +import android.view.MotionEvent; import com.android.launcher3.R; import com.android.launcher3.anim.AnimationSuccessListener; @@ -97,6 +100,7 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler 1 - mLauncherAlpha.value); } else { @@ -198,15 +203,27 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler { + if (!mTouchedHomeDuringTransition) { + // If the user hasn't interacted with the screen during the transition, + // send a home intent so launcher can go to the default home screen. + // (If they are trying to touch something, we don't want to interfere.) + mContext.startActivity(mGestureState.getHomeIntent()); + } + }, true); } break; } @@ -389,6 +413,8 @@ public class FallbackSwipeHandler extends BaseSwipeUpHandler Date: Fri, 6 Dec 2019 11:08:56 -0800 Subject: [PATCH 11/30] Moving processing data received from Wellbeing to a supress-all block Bug: 144927220 Change-Id: I25f02d5844b32abb73c2b4b6fcdaa68f438e6aa4 --- .../launcher3/model/WellbeingModel.java | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java index 5aa43888b4..c86139b30f 100644 --- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java +++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java @@ -229,6 +229,31 @@ public final class WellbeingModel { params.putInt(EXTRA_MAX_NUM_ACTIONS_SHOWN, 1); // Perform wellbeing call . remoteActionBundle = client.call(METHOD_GET_ACTIONS, null, params); + + synchronized (mModelLock) { + // Remove the entries for requested packages, and then update the fist with what we + // got from service + Arrays.stream(packageNames).forEach(mPackageToActionId::remove); + + // The result consists of sub-bundles, each one is per a remote action. Each + // sub-bundle has a RemoteAction and a list of packages to which the action applies. + for (String actionId : + remoteActionBundle.getStringArray(EXTRA_ACTIONS)) { + final Bundle actionBundle = remoteActionBundle.getBundle(actionId); + mActionIdMap.put(actionId, + actionBundle.getParcelable(EXTRA_ACTION)); + + final String[] packagesForAction = + actionBundle.getStringArray(EXTRA_PACKAGES); + if (DEBUG || mIsInTest) { + Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ", + packagesForAction)); + } + for (String packageName : packagesForAction) { + mPackageToActionId.put(packageName, actionId); + } + } + } } catch (DeadObjectException e) { Log.i(TAG, "retrieveActions(): DeadObjectException"); return false; @@ -237,31 +262,6 @@ public final class WellbeingModel { if (mIsInTest) throw new RuntimeException(e); return true; } - - synchronized (mModelLock) { - // Remove the entries for requested packages, and then update the fist with what we - // got from service - Arrays.stream(packageNames).forEach(mPackageToActionId::remove); - - // The result consists of sub-bundles, each one is per a remote action. Each sub-bundle - // has a RemoteAction and a list of packages to which the action applies. - for (String actionId : - remoteActionBundle.getStringArray(EXTRA_ACTIONS)) { - final Bundle actionBundle = remoteActionBundle.getBundle(actionId); - mActionIdMap.put(actionId, - actionBundle.getParcelable(EXTRA_ACTION)); - - final String[] packagesForAction = - actionBundle.getStringArray(EXTRA_PACKAGES); - if (DEBUG || mIsInTest) { - Log.d(TAG, "....actionId: " + actionId + ", packages: " + String.join(", ", - packagesForAction)); - } - for (String packageName : packagesForAction) { - mPackageToActionId.put(packageName, actionId); - } - } - } if (DEBUG || mIsInTest) Log.i(TAG, "retrieveActions(): finished"); return true; } From a591bf170d5824a3d23101458c4da52747278ff4 Mon Sep 17 00:00:00 2001 From: Samuel Fufa Date: Fri, 6 Dec 2019 13:30:38 -0800 Subject: [PATCH 12/30] Add logging for hybrid hotseat Bug: 142753423 Test: Manual Change-Id: I5abacd64c43130af54314d3c2fd07407e39d6af9 --- .../HotseatPredictionController.java | 28 +++++++++++++++++++ .../PredictionUiStateManager.java | 6 ++++ 2 files changed, 34 insertions(+) diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java index 0254340795..f7e71f3731 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/HotseatPredictionController.java @@ -32,6 +32,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.allapps.AllAppsStore; @@ -45,11 +46,13 @@ import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.uioverrides.PredictedAppIcon; import com.android.launcher3.uioverrides.QuickstepLauncher; +import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.util.ComponentKey; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.IntStream; /** * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows @@ -73,6 +76,7 @@ public class HotseatPredictionController implements DragController.DragListener, private DropTarget.DragObject mDragObject; private int mHotSeatItemsCount; + private int mPredictedSpotsCount = 0; private Launcher mLauncher; private Hotseat mHotseat; @@ -86,6 +90,8 @@ public class HotseatPredictionController implements DragController.DragListener, private List mOutlineDrawings = new ArrayList<>(); + private static HotseatPredictionController sInstance; + public HotseatPredictionController(Launcher launcher) { mLauncher = launcher; mHotseat = launcher.getHotseat(); @@ -95,6 +101,7 @@ public class HotseatPredictionController implements DragController.DragListener, mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons; launcher.getDeviceProfile().inv.addOnChangeListener(this); mHotseat.addOnAttachStateChangeListener(this); + sInstance = this; } @Override @@ -144,6 +151,7 @@ public class HotseatPredictionController implements DragController.DragListener, } preparePredictionInfo(predictedItem, rank); } + mPredictedSpotsCount = predictionIndex; bindItems(newItems, animate, callback); } @@ -421,6 +429,26 @@ public class HotseatPredictionController implements DragController.DragListener, } } + /** + * Fill in predicted_rank field based on app prediction. + * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT + */ + public static void fillInHybridHotseatRank( + @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) { + if (sInstance == null || itemInfo.getTargetComponent() == null + || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { + return; + } + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); + + final List predictedApps = sInstance.mComponentKeyMappers; + IntStream.range(0, predictedApps.size()) + .filter((i) -> k.equals(predictedApps.get(i).getComponentKey())) + .findFirst() + .ifPresent((rank) -> target.predictedRank = + Integer.parseInt(sInstance.mPredictedSpotsCount + "0" + rank)); + } + private static boolean isPredictedIcon(View view) { return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) view.getTag()).container diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java index f9ee701ef2..e45eded161 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java @@ -26,6 +26,7 @@ import android.content.Context; import androidx.annotation.NonNull; +import com.android.launcher3.HotseatPredictionController; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener; import com.android.launcher3.ItemInfo; @@ -316,6 +317,11 @@ public class PredictionUiStateManager implements StateListener, ItemInfoUpdateRe && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) { return; } + if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) { + HotseatPredictionController.fillInHybridHotseatRank(itemInfo, target); + return; + } + final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user); final List predictedApps = manager.getCurrentState().apps; IntStream.range(0, predictedApps.size()) From 4b5b0eb60d9c83a20fc6ab4f48d57a121a78fe78 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 2 Dec 2019 17:00:35 -0800 Subject: [PATCH 13/30] Moving some tests to robolectric > Adding support for simulating model load Bug: 130562632 Change-Id: I1de8c0abe2e74d4e7e47e18914316c339920609a --- robolectric_tests/Android.mk | 4 +- .../model/BaseModelUpdateTaskTestCase.java | 12 +- .../model/DefaultLayoutProviderTest.java | 165 ++++++++++++++++++ .../shadows/LShadowLauncherApps.java | 96 ++++++++++ .../launcher3/shadows/LShadowUserManager.java | 53 ++++++ .../shadows/ShadowLooperExecutor.java | 68 ++++++++ .../launcher3/util/LauncherLayoutBuilder.java | 0 .../launcher3/util/TestLauncherProvider.java | 20 ++- .../android/launcher3/LauncherProvider.java | 20 ++- .../android/launcher3/model/LoaderTask.java | 5 +- .../ui/DefaultLayoutProviderTest.java | 126 ------------- 11 files changed, 425 insertions(+), 144 deletions(-) create mode 100644 robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java create mode 100644 robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java rename {tests => robolectric_tests}/src/com/android/launcher3/util/LauncherLayoutBuilder.java (100%) delete mode 100644 tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk index 62915f2552..310d43cba4 100644 --- a/robolectric_tests/Android.mk +++ b/robolectric_tests/Android.mk @@ -27,7 +27,7 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ mockito-robolectric-prebuilt \ truth-prebuilt LOCAL_JAVA_LIBRARIES := \ - platform-robolectric-3.6.1-prebuilt + platform-robolectric-4.3-prebuilt LOCAL_JAVA_RESOURCE_DIRS := resources config @@ -54,4 +54,4 @@ LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src \ LOCAL_ROBOTEST_TIMEOUT := 36000 -include prebuilts/misc/common/robolectric/3.6.1/run_robotests.mk +include prebuilts/misc/common/robolectric/4.3/run_robotests.mk diff --git a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index 5b6d94d732..f7e05a41dd 100644 --- a/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/robolectric_tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -1,10 +1,13 @@ package com.android.launcher3.model; +import static com.android.launcher3.shadows.ShadowLooperExecutor.reinitializeStaticExecutors; + import static org.mockito.Matchers.anyBoolean; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.robolectric.util.ReflectionHelpers.setField; import android.content.ComponentName; import android.content.Context; @@ -29,6 +32,7 @@ import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.cache.CachingLogic; import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.pm.PackageInstallerCompat; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.TestLauncherProvider; @@ -53,7 +57,7 @@ import java.util.function.Supplier; public class BaseModelUpdateTaskTestCase { public final HashMap> fieldCache = new HashMap<>(); - private TestLauncherProvider mProvider; + public TestLauncherProvider provider; public Context targetContext; public UserHandle myUser; @@ -71,9 +75,11 @@ public class BaseModelUpdateTaskTestCase { @Before public void setUp() throws Exception { ShadowLog.stream = System.out; + reinitializeStaticExecutors(); + setField(PackageInstallerCompat.class, null, "sInstance", null); - mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class); - ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider); + provider = Robolectric.setupContentProvider(TestLauncherProvider.class); + ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider); callbacks = mock(Callbacks.class); appState = mock(LauncherAppState.class); diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java new file mode 100644 index 0000000000..9e4a43c1b6 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2019 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.model; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.robolectric.Shadows.shadowOf; +import static org.robolectric.util.ReflectionHelpers.setField; + +import android.content.ComponentName; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageInstaller.SessionParams; +import android.net.Uri; +import android.provider.Settings; + +import com.android.launcher3.FolderInfo; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherProvider; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.shadows.LShadowLauncherApps; +import com.android.launcher3.shadows.LShadowUserManager; +import com.android.launcher3.shadows.ShadowLooperExecutor; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LauncherLayoutBuilder; +import com.android.launcher3.widget.custom.CustomWidgetManager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.LooperMode; +import org.robolectric.annotation.LooperMode.Mode; +import org.robolectric.shadows.ShadowPackageManager; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.OutputStreamWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * Tests for layout parser for remote layout + */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {LShadowUserManager.class, LShadowLauncherApps.class, ShadowLooperExecutor.class}) +@LooperMode(Mode.PAUSED) +public class DefaultLayoutProviderTest extends BaseModelUpdateTaskTestCase { + + private static final String SETTINGS_APP = "com.android.settings"; + private static final String TEST_PROVIDER_AUTHORITY = + DefaultLayoutProviderTest.class.getName().toLowerCase(); + + private static final int BITMAP_SIZE = 10; + private static final int GRID_SIZE = 4; + + @Before + public void setUp() throws Exception { + super.setUp(); + InvariantDeviceProfile.INSTANCE.initializeForTesting(idp); + CustomWidgetManager.INSTANCE.initializeForTesting(mock(CustomWidgetManager.class)); + + idp.numRows = idp.numColumns = idp.numHotseatIcons = GRID_SIZE; + idp.iconBitmapSize = BITMAP_SIZE; + + provider.setAllowLoadDefaultFavorites(true); + Settings.Secure.putString(targetContext.getContentResolver(), + "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY); + + ShadowPackageManager spm = shadowOf(targetContext.getPackageManager()); + spm.addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority = + TEST_PROVIDER_AUTHORITY; + spm.addActivityIfNotPresent(new ComponentName(SETTINGS_APP, SETTINGS_APP)); + } + + @After + public void cleanup() { + InvariantDeviceProfile.INSTANCE.initializeForTesting(null); + CustomWidgetManager.INSTANCE.initializeForTesting(null); + } + + @Test + public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0) + .putApp(SETTINGS_APP, SETTINGS_APP)); + + // Verify one item in hotseat + assertEquals(1, bgDataModel.workspaceItems.size()); + ItemInfo info = bgDataModel.workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType); + } + + @Test + public void testCustomProfileLoaded_with_folder() throws Exception { + writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) + .addApp(SETTINGS_APP, SETTINGS_APP) + .addApp(SETTINGS_APP, SETTINGS_APP) + .addApp(SETTINGS_APP, SETTINGS_APP) + .build()); + + // Verify folder + assertEquals(1, bgDataModel.workspaceItems.size()); + ItemInfo info = bgDataModel.workspaceItems.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType); + assertEquals(3, ((FolderInfo) info).contents.size()); + } + + @Test + public void testCustomProfileLoaded_with_widget() throws Exception { + String pendingAppPkg = "com.test.pending"; + + // Add a dummy session info so that the widget exists + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName(pendingAppPkg); + + PackageInstaller installer = targetContext.getPackageManager().getPackageInstaller(); + int sessionId = installer.createSession(params); + SessionInfo sessionInfo = installer.getSessionInfo(sessionId); + setField(sessionInfo, "installerPackageName", "com.test"); + setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON); + + writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0) + .putWidget(pendingAppPkg, "DummyWidget", 2, 2)); + + // Verify widget + assertEquals(1, bgDataModel.appWidgets.size()); + ItemInfo info = bgDataModel.appWidgets.get(0); + assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType); + assertEquals(2, info.spanX); + assertEquals(2, info.spanY); + } + + private void writeLayoutAndLoad(LauncherLayoutBuilder builder) throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + builder.build(new OutputStreamWriter(bos)); + + Uri layoutUri = LauncherProvider.getLayoutUri(TEST_PROVIDER_AUTHORITY, targetContext); + shadowOf(targetContext.getContentResolver()).registerInputStream(layoutUri, + new ByteArrayInputStream(bos.toByteArray())); + + LoaderResults results = new LoaderResults(appState, bgDataModel, allAppsList, 0, + new WeakReference<>(callbacks)); + LoaderTask task = new LoaderTask(appState, allAppsList, bgDataModel, results); + Executors.MODEL_EXECUTOR.submit(() -> task.loadWorkspace(new ArrayList<>())).get(); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java new file mode 100644 index 0000000000..204ec9bd17 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2019 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.shadows; + +import static org.robolectric.util.ReflectionHelpers.ClassParameter; +import static org.robolectric.util.ReflectionHelpers.callConstructor; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ShortcutInfo; +import android.os.Process; +import android.os.UserHandle; +import android.util.ArraySet; + +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.PackageUserKey; + +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowLauncherApps; + +import java.util.Collections; +import java.util.List; + +/** + * Extension of {@link ShadowLauncherApps} with missing shadow methods + */ +@Implements(value = LauncherApps.class) +public class LShadowLauncherApps extends ShadowLauncherApps { + + public final ArraySet disabledApps = new ArraySet<>(); + public final ArraySet disabledActivities = new ArraySet<>(); + + @Implementation + @Override + protected List getShortcuts(LauncherApps.ShortcutQuery query, UserHandle user) { + try { + return super.getShortcuts(query, user); + } catch (UnsupportedOperationException e) { + return Collections.emptyList(); + } + } + + @Implementation + protected boolean isPackageEnabled(String packageName, UserHandle user) { + return !disabledApps.contains(new PackageUserKey(packageName, user)); + } + + @Implementation + protected boolean isActivityEnabled(ComponentName component, UserHandle user) { + return !disabledActivities.contains(new ComponentKey(component, user)); + } + + @Implementation + protected LauncherActivityInfo resolveActivity(Intent intent, UserHandle user) { + ResolveInfo ri = RuntimeEnvironment.application.getPackageManager() + .resolveActivity(intent, 0); + return getLauncherActivityInfo(ri.activityInfo); + } + + public LauncherActivityInfo getLauncherActivityInfo(ActivityInfo activityInfo) { + return callConstructor(LauncherActivityInfo.class, + ClassParameter.from(Context.class, RuntimeEnvironment.application), + ClassParameter.from(ActivityInfo.class, activityInfo), + ClassParameter.from(UserHandle.class, Process.myUserHandle())); + } + + @Implementation + public ApplicationInfo getApplicationInfo(String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException { + return RuntimeEnvironment.application.getPackageManager() + .getApplicationInfo(packageName, flags); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java new file mode 100644 index 0000000000..edf8edbb43 --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowUserManager.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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.shadows; + +import android.os.UserHandle; +import android.os.UserManager; +import android.util.SparseBooleanArray; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowUserManager; + +/** + * Extension of {@link ShadowUserManager} with missing shadow methods + */ +@Implements(value = UserManager.class) +public class LShadowUserManager extends ShadowUserManager { + + private final SparseBooleanArray mQuietUsers = new SparseBooleanArray(); + private final SparseBooleanArray mLockedUsers = new SparseBooleanArray(); + + @Implementation + protected boolean isQuietModeEnabled(UserHandle userHandle) { + return mQuietUsers.get(userHandle.hashCode()); + } + + public void setQuietModeEnabled(UserHandle userHandle, boolean enabled) { + mQuietUsers.put(userHandle.hashCode(), enabled); + } + + @Implementation + protected boolean isUserUnlocked(UserHandle userHandle) { + return !mLockedUsers.get(userHandle.hashCode()); + } + + public void setUserLocked(UserHandle userHandle, boolean enabled) { + mLockedUsers.put(userHandle.hashCode(), enabled); + } +} diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java new file mode 100644 index 0000000000..d56de3c9cb --- /dev/null +++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 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.shadows; + +import static com.android.launcher3.util.Executors.createAndStartNewLooper; + +import static org.robolectric.shadow.api.Shadow.invokeConstructor; +import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; +import static org.robolectric.util.ReflectionHelpers.setField; + +import android.os.Handler; +import android.os.Looper; + +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.LooperExecutor; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Shadow for {@link LooperExecutor} to provide reset functionality for static executors. + */ +@Implements(value = LooperExecutor.class, isInAndroidSdk = false) +public class ShadowLooperExecutor { + + // Keep reference to all created Loopers so they can be torn down after test + private static Set executors = + Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + + @RealObject private LooperExecutor realExecutor; + + @Implementation + protected void __constructor__(Looper looper) { + invokeConstructor(LooperExecutor.class, realExecutor, from(Looper.class, looper)); + executors.add(realExecutor); + } + + /** + * Re-initializes any executor which may have been reset when a test finished + */ + public static void reinitializeStaticExecutors() { + for (LooperExecutor executor : new ArrayList<>(executors)) { + setField(executor, "mHandler", + new Handler(createAndStartNewLooper(executor.getThread().getName()))); + } + } +} diff --git a/tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java b/robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java similarity index 100% rename from tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java rename to robolectric_tests/src/com/android/launcher3/util/LauncherLayoutBuilder.java diff --git a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java index a9c1a7c9d8..7e873e8fa2 100644 --- a/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java +++ b/robolectric_tests/src/com/android/launcher3/util/TestLauncherProvider.java @@ -10,6 +10,8 @@ import com.android.launcher3.LauncherProvider; */ public class TestLauncherProvider extends LauncherProvider { + private boolean mAllowLoadDefaultFavorites; + @Override public boolean onCreate() { return true; @@ -18,18 +20,26 @@ public class TestLauncherProvider extends LauncherProvider { @Override protected synchronized void createDbIfNotExists() { if (mOpenHelper == null) { - mOpenHelper = new MyDatabaseHelper(getContext()); + mOpenHelper = new MyDatabaseHelper(getContext(), mAllowLoadDefaultFavorites); } } + public void setAllowLoadDefaultFavorites(boolean allowLoadDefaultFavorites) { + mAllowLoadDefaultFavorites = allowLoadDefaultFavorites; + } + public SQLiteDatabase getDb() { createDbIfNotExists(); return mOpenHelper.getWritableDatabase(); } private static class MyDatabaseHelper extends DatabaseHelper { - public MyDatabaseHelper(Context context) { + + private final boolean mAllowLoadDefaultFavorites; + + MyDatabaseHelper(Context context, boolean allowLoadDefaultFavorites) { super(context, null); + mAllowLoadDefaultFavorites = allowLoadDefaultFavorites; initIds(); } @@ -39,7 +49,11 @@ public class TestLauncherProvider extends LauncherProvider { } @Override - protected void onEmptyDbCreated() { } + protected void onEmptyDbCreated() { + if (mAllowLoadDefaultFavorites) { + super.onEmptyDbCreated(); + } + } @Override protected void handleOneTimeDataUpgrade(SQLiteDatabase db) { } diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 42927ea1cf..67fe0384b0 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -484,8 +484,6 @@ public class LauncherProvider extends ContentProvider { */ private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) { Context ctx = getContext(); - InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx); - String authority = Settings.Secure.getString(ctx.getContentResolver(), "launcher3.layout.provider"); if (TextUtils.isEmpty(authority)) { @@ -497,13 +495,7 @@ public class LauncherProvider extends ContentProvider { Log.e(TAG, "No provider found for authority " + authority); return null; } - Uri uri = new Uri.Builder().scheme("content").authority(authority).path("launcher_layout") - .appendQueryParameter("version", "1") - .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns)) - .appendQueryParameter("gridHeight", Integer.toString(grid.numRows)) - .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons)) - .build(); - + Uri uri = getLayoutUri(authority, ctx); try (InputStream in = ctx.getContentResolver().openInputStream(uri)) { // Read the full xml so that we fail early in case of any IO error. String layout = new String(IOUtils.toByteArray(in)); @@ -520,6 +512,16 @@ public class LauncherProvider extends ContentProvider { } } + public static Uri getLayoutUri(String authority, Context ctx) { + InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx); + return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout") + .appendQueryParameter("version", "1") + .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns)) + .appendQueryParameter("gridHeight", Integer.toString(grid.numRows)) + .appendQueryParameter("hotseatSize", Integer.toString(grid.numHotseatIcons)) + .build(); + } + private DefaultLayoutParser getDefaultLayoutParser(AppWidgetHost widgetHost) { InvariantDeviceProfile idp = LauncherAppState.getIDP(getContext()); int defaultLayout = idp.defaultLayoutId; diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 3a4085c89c..2754cfc6b8 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -42,6 +42,8 @@ import android.util.LongSparseArray; import android.util.MutableInt; import android.util.TimingLogger; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; import com.android.launcher3.InstallShortcutReceiver; @@ -271,7 +273,8 @@ public class LoaderTask implements Runnable { this.notify(); } - private void loadWorkspace(List allDeepShortcuts) { + @VisibleForTesting + void loadWorkspace(List allDeepShortcuts) { final Context context = mApp.getContext(); final ContentResolver contentResolver = context.getContentResolver(); final PackageManagerHelper pmHelper = new PackageManagerHelper(context); diff --git a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java b/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java deleted file mode 100644 index 1d89d6edcb..0000000000 --- a/tests/src/com/android/launcher3/ui/DefaultLayoutProviderTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (C) 2019 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.ui; - -import static org.junit.Assert.assertTrue; - -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.pm.ProviderInfo; -import android.net.Uri; -import android.os.ParcelFileDescriptor; -import android.os.ParcelFileDescriptor.AutoCloseOutputStream; - -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.launcher3.LauncherAppWidgetProviderInfo; -import com.android.launcher3.testcomponent.TestCommandProvider; -import com.android.launcher3.util.LauncherLayoutBuilder; -import com.android.launcher3.util.rule.ShellCommandRule; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.OutputStreamWriter; - -@MediumTest -@RunWith(AndroidJUnit4.class) -public class DefaultLayoutProviderTest extends AbstractLauncherUiTest { - - @Rule - public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); - - private static final String SETTINGS_APP = "com.android.settings"; - - private Context mContext; - private String mAuthority; - - @Before - @Override - public void setUp() throws Exception { - super.setUp(); - - mContext = InstrumentationRegistry.getContext(); - - PackageManager pm = mTargetContext.getPackageManager(); - ProviderInfo pi = pm.getProviderInfo(new ComponentName(mContext, - TestCommandProvider.class), 0); - mAuthority = pi.authority; - } - - @Test - public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception { - writeLayout(new LauncherLayoutBuilder().atHotseat(0).putApp(SETTINGS_APP, SETTINGS_APP)); - - // Launch the home activity - mDevice.pressHome(); - - mLauncher.getWorkspace().getHotseatAppIcon(getSettingsApp().getLabel().toString()); - } - - @Test - public void testCustomProfileLoaded_with_widget() throws Exception { - // A non-restored widget with no config screen gets restored automatically. - LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(this, false); - - writeLayout(new LauncherLayoutBuilder().atWorkspace(0, 1, 0) - .putWidget(info.getComponent().getPackageName(), - info.getComponent().getClassName(), 2, 2)); - - // Launch the home activity - mDevice.pressHome(); - - // Verify widget present - assertTrue("Widget is not present", - mLauncher.getWorkspace().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null); - } - - @Test - public void testCustomProfileLoaded_with_folder() throws Exception { - writeLayout(new LauncherLayoutBuilder().atHotseat(0).putFolder(android.R.string.copy) - .addApp(SETTINGS_APP, SETTINGS_APP) - .addApp(SETTINGS_APP, SETTINGS_APP) - .addApp(SETTINGS_APP, SETTINGS_APP) - .build()); - - // Launch the home activity - mDevice.pressHome(); - - mLauncher.getWorkspace().getHotseatFolder("Folder: Copy"); - } - - @After - public void cleanup() throws Exception { - mDevice.executeShellCommand("settings delete secure launcher3.layout.provider"); - } - - private void writeLayout(LauncherLayoutBuilder builder) throws Exception { - mDevice.executeShellCommand("settings put secure launcher3.layout.provider " + mAuthority); - ParcelFileDescriptor pfd = mTargetContext.getContentResolver().openFileDescriptor( - Uri.parse("content://" + mAuthority + "/launcher_layout"), "w"); - - try (OutputStreamWriter writer = new OutputStreamWriter(new AutoCloseOutputStream(pfd))) { - builder.build(writer); - } - clearLauncherData(); - } -} From bdaa179b928f166cebf419b722687507f2eb0f3c Mon Sep 17 00:00:00 2001 From: vadimt Date: Fri, 6 Dec 2019 19:36:44 -0800 Subject: [PATCH 14/30] Consistently checking state change messages Checking for events whenever Launcher sends them. Checking for correct events (final events, not for events from intermediate state changes). This should simplify diagnosing of bugs involving TAPL. This is also supposed to fix Fallback overview tests. Bug: 143488140 Change-Id: If053ed808ec71bf2b652ab680be5bdfe9ff8cbb9 --- .../android/quickstep/RecentsActivity.java | 8 +++ .../quickstep/FallbackRecentsTest.java | 10 ++-- .../android/launcher3/tapl/Background.java | 26 ++++----- .../tapl/com/android/launcher3/tapl/Home.java | 3 +- .../android/launcher3/tapl/Launchable.java | 2 +- .../tapl/LauncherInstrumentation.java | 54 +++++++++++++------ .../android/launcher3/tapl/OverviewTask.java | 4 +- .../com/android/launcher3/tapl/Workspace.java | 23 +++++--- 8 files changed, 85 insertions(+), 45 deletions(-) diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java index e0e20ee27e..3f5179f76c 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java @@ -18,6 +18,7 @@ package com.android.quickstep; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_DURATION; import static com.android.launcher3.QuickstepAppTransitionManagerImpl.STATUS_BAR_TRANSITION_PRE_DELAY; +import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator; import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; @@ -38,6 +39,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAnimationRunner; import com.android.launcher3.R; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.views.BaseDragLayer; import com.android.quickstep.fallback.FallbackRecentsView; @@ -217,6 +219,12 @@ public final class RecentsActivity extends BaseRecentsActivity { mFallbackRecentsView.reset(); } + @Override + protected void onResume() { + super.onResume(); + AccessibilityManagerCompat.sendStateEventToTest(getBaseContext(), OVERVIEW_STATE_ORDINAL); + } + public void onTaskLaunched() { mFallbackRecentsView.resetTaskVisuals(); } diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java index 85cffaa107..abd2180d85 100644 --- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java +++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java @@ -31,6 +31,8 @@ import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification; import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand; +import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL; +import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -57,10 +59,10 @@ import com.android.launcher3.tapl.TestHelpers; import com.android.launcher3.testcomponent.TestCommandReceiver; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.FailureWatcher; +import com.android.launcher3.util.rule.TestStabilityRule; import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch; import com.android.quickstep.views.RecentsView; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -130,7 +132,7 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test - @Ignore // b/143488140 + @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT) // b/143488140 public void goToOverviewFromHome() { mDevice.pressHome(); assertTrue("Fallback Launcher not visible", mDevice.wait(Until.hasObject(By.pkg( @@ -141,7 +143,7 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test - @Ignore // b/143488140 + @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT) // b/143488140 public void goToOverviewFromApp() { startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); @@ -176,7 +178,7 @@ public class FallbackRecentsTest { @NavigationModeSwitch @Test - @Ignore // b/143488140 + @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT) // b/143488140 public void testOverview() { startAppFastAndWaitForRecentTask(getAppPackageName()); startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR)); diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java index 6583d32189..d9ae778c7c 100644 --- a/tests/tapl/com/android/launcher3/tapl/Background.java +++ b/tests/tapl/com/android/launcher3/tapl/Background.java @@ -17,6 +17,7 @@ package com.android.launcher3.tapl; import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; +import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; import android.graphics.Point; import android.os.SystemClock; @@ -54,13 +55,13 @@ public class Background extends LauncherInstrumentation.VisibleContainer { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to switch from background to overview")) { verifyActiveContainer(); - goToOverviewUnchecked(BACKGROUND_APP_STATE_ORDINAL); + goToOverviewUnchecked(); return mLauncher.isFallbackOverview() ? new BaseOverview(mLauncher) : new Overview(mLauncher); } } - protected void goToOverviewUnchecked(int expectedState) { + protected void goToOverviewUnchecked() { switch (mLauncher.getNavigationModel()) { case ZERO_BUTTON: { final int centerX = mLauncher.getDevice().getDisplayWidth() / 2; @@ -81,9 +82,11 @@ public class Background extends LauncherInstrumentation.VisibleContainer { start, end), event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(event.getClassName()), - "Pause wasn't detected"); - mLauncher.sendPointer( - downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end); + () -> "Pause wasn't detected"); + mLauncher.runToState( + () -> mLauncher.sendPointer( + downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end), + OVERVIEW_STATE_ORDINAL); break; } @@ -105,17 +108,14 @@ public class Background extends LauncherInstrumentation.VisibleContainer { startY = endY = mLauncher.getDevice().getDisplayHeight() / 2; } - if (mLauncher.isFallbackOverview()) { - mLauncher.linearGesture(startX, startY, endX, endY, 10, false); - new BaseOverview(mLauncher); - } else { - mLauncher.swipeToState(startX, startY, endX, endY, 10, expectedState); - } + mLauncher.swipeToState(startX, startY, endX, endY, 10, OVERVIEW_STATE_ORDINAL); break; } case THREE_BUTTON: - mLauncher.waitForSystemUiObject("recent_apps").click(); + mLauncher.runToState( + () -> mLauncher.waitForSystemUiObject("recent_apps").click(), + OVERVIEW_STATE_ORDINAL); break; } } @@ -167,7 +167,7 @@ public class Background extends LauncherInstrumentation.VisibleContainer { case THREE_BUTTON: // Double press the recents button. UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps"); - recentsButton.click(); + mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL); mLauncher.getOverview(); recentsButton.click(); break; diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java index e0fe933ee9..1e4d937a81 100644 --- a/tests/tapl/com/android/launcher3/tapl/Home.java +++ b/tests/tapl/com/android/launcher3/tapl/Home.java @@ -16,7 +16,6 @@ package com.android.launcher3.tapl; -import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL; import androidx.annotation.NonNull; @@ -52,7 +51,7 @@ public abstract class Home extends Background { try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "want to switch from home to overview")) { verifyActiveContainer(); - goToOverviewUnchecked(OVERVIEW_STATE_ORDINAL); + goToOverviewUnchecked(); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( "performed the switch action")) { return new Overview(mLauncher); diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index df80a51fd7..6881197b33 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -56,7 +56,7 @@ abstract class Launchable { mLauncher.executeAndWaitForEvent( () -> mObject.click(), event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, - "Launching an app didn't open a new window: " + mObject.getText()); + () -> "Launching an app didn't open a new window: " + mObject.getText()); mLauncher.assertTrue( "App didn't start: " + selector, diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 727d757305..2fea1b76e8 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.MATCH_ALL; import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static com.android.launcher3.tapl.TestHelpers.getOverviewPackageName; -import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL; import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; import android.app.ActivityManager; @@ -78,6 +77,8 @@ import java.util.List; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; /** * The main tapl object. The only object that can be explicitly constructed by the using code. It @@ -510,7 +511,7 @@ public final class LauncherInstrumentation { } Parcelable executeAndWaitForEvent(Runnable command, - UiAutomation.AccessibilityEventFilter eventFilter, String message) { + UiAutomation.AccessibilityEventFilter eventFilter, Supplier message) { try { final AccessibilityEvent event = mInstrumentation.getUiAutomation().executeAndWaitForEvent( @@ -518,7 +519,7 @@ public final class LauncherInstrumentation { assertNotNull("executeAndWaitForEvent returned null (this can't happen)", event); return event.getParcelableData(); } catch (TimeoutException e) { - fail(message); + fail(message.get()); return null; } } @@ -557,15 +558,12 @@ public final class LauncherInstrumentation { log("Hierarchy before swiping up to home"); dumpViewHierarchy(); log(action = "swiping up to home from " + getVisibleStateMessage()); - final int finalState = mDevice.hasObject(By.pkg(getLauncherPackageName())) - || isFallbackOverview() - ? NORMAL_STATE_ORDINAL : BACKGROUND_APP_STATE_ORDINAL; try (LauncherInstrumentation.Closable c = addContextLayer(action)) { swipeToState( displaySize.x / 2, displaySize.y - 1, displaySize.x / 2, 0, - ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, finalState); + ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL); } } } else { @@ -576,7 +574,7 @@ public final class LauncherInstrumentation { waitForSystemUiObject("home").click(); }, event -> true, - "Pressing Home didn't produce any events"); + () -> "Pressing Home didn't produce any events"); mDevice.waitForIdle(); } try (LauncherInstrumentation.Closable c = addContextLayer( @@ -771,14 +769,38 @@ public final class LauncherInstrumentation { return mDevice; } + private static String eventListToString(List actualEvents) { + if (actualEvents.isEmpty()) return "no events"; + + return "[" + + actualEvents.stream() + .map(state -> TestProtocol.stateOrdinalToString(state)) + .collect(Collectors.joining(", ")) + + "]"; + } + + void runToState(Runnable command, int expectedState) { + final List actualEvents = new ArrayList<>(); + executeAndWaitForEvent( + command, + event -> isSwitchToStateEvent(event, expectedState, actualEvents), + () -> "Failed to receive an event for the swipe end: expected " + + TestProtocol.stateOrdinalToString(expectedState) + + ", actual: " + eventListToString(actualEvents)); + } + + private boolean isSwitchToStateEvent( + AccessibilityEvent event, int expectedState, List actualEvents) { + if (!TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName())) return false; + + final Bundle parcel = (Bundle) event.getParcelableData(); + final int actualState = parcel.getInt(TestProtocol.STATE_FIELD); + actualEvents.add(actualState); + return actualState == expectedState; + } + void swipeToState(int startX, int startY, int endX, int endY, int steps, int expectedState) { - final Bundle parcel = (Bundle) executeAndWaitForEvent( - () -> linearGesture(startX, startY, endX, endY, steps, false), - event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()), - "Swipe failed to receive an event for the swipe end"); - assertEquals("Swipe switched launcher to a wrong state;", - TestProtocol.stateOrdinalToString(expectedState), - TestProtocol.stateOrdinalToString(parcel.getInt(TestProtocol.STATE_FIELD))); + runToState(() -> linearGesture(startX, startY, endX, endY, steps, false), expectedState); } int getBottomGestureSize() { @@ -864,7 +886,7 @@ public final class LauncherInstrumentation { executeAndWaitForEvent( () -> linearGesture(startX, startY, endX, endY, steps, slowDown), event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()), - "Didn't receive a scroll end message: " + startX + ", " + startY + () -> "Didn't receive a scroll end message: " + startX + ", " + startY + ", " + endX + ", " + endY); } diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 2ee424bc52..46f8ba57a4 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -67,8 +67,8 @@ public final class OverviewTask { mLauncher.executeAndWaitForEvent( () -> mTask.click(), event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, - "Launching task didn't open a new window: " + - mTask.getParent().getContentDescription()); + () -> "Launching task didn't open a new window: " + + mTask.getParent().getContentDescription()); } return new Background(mLauncher); } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 81d343d38b..8a53ef12b6 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -17,6 +17,8 @@ package com.android.launcher3.tapl; import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; +import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL; +import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL; import static junit.framework.TestCase.assertTrue; @@ -165,14 +167,21 @@ public final class Workspace extends Home { LauncherInstrumentation.log("dragIconToWorkspace: begin"); final Point launchableCenter = launchable.getObject().getVisibleCenter(); final long downTime = SystemClock.uptimeMillis(); - launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, launchableCenter); - LauncherInstrumentation.log("dragIconToWorkspace: sent down"); - launcher.waitForLauncherObject(longPressIndicator); - LauncherInstrumentation.log("dragIconToWorkspace: indicator"); - launcher.movePointer(launchableCenter, dest, 10, downTime, true); + launcher.runToState( + () -> { + launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, + launchableCenter); + LauncherInstrumentation.log("dragIconToWorkspace: sent down"); + launcher.waitForLauncherObject(longPressIndicator); + LauncherInstrumentation.log("dragIconToWorkspace: indicator"); + launcher.movePointer(launchableCenter, dest, 10, downTime, true); + }, + SPRING_LOADED_STATE_ORDINAL); LauncherInstrumentation.log("dragIconToWorkspace: moved pointer"); - launcher.sendPointer( - downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest); + launcher.runToState( + () -> launcher.sendPointer( + downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, dest), + NORMAL_STATE_ORDINAL); LauncherInstrumentation.log("dragIconToWorkspace: end"); launcher.waitUntilGone("drop_target_bar"); } From bc5d7e68e8a70a8ca350342fa9037ad9ac0dc7fd Mon Sep 17 00:00:00 2001 From: vadimt Date: Mon, 9 Dec 2019 13:53:05 -0800 Subject: [PATCH 15/30] Diagnosing the reason for opening context menu Adding it permanently. System tests using TAPL sometimes run into mysterious hard-to-diagnose problems (like, a menu opens for no apparent reason), so we'll need to keep diags like this forever. Change-Id: I25fcab94931fa4f6e1bda34d5705de5dd411188a --- src/com/android/launcher3/Launcher.java | 4 ++++ src/com/android/launcher3/testing/TestProtocol.java | 2 ++ src/com/android/launcher3/touch/WorkspaceTouchListener.java | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index cb8fe05c0e..faf49f72af 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -122,6 +122,7 @@ import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.qsb.QsbContainerView; import com.android.launcher3.states.RotationHelper; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.AllAppsSwipeController; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper; @@ -2636,6 +2637,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // Setting the touch point to (-1, -1) will show the options popup in the center of // the screen. + if (Utilities.IS_RUNNING_IN_TEST_HARNESS) { + Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up"); + } OptionsPopupView.showDefaultOptions(this, -1, -1); } return true; diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 832f7f0990..5aae841e22 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -83,6 +83,8 @@ public final class TestProtocol { public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing"; public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing"; + public static final String PERMANENT_DIAG_TAG = "TaplTarget"; + public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824"; public static final String NO_DRAG_TO_WORKSPACE = "b/138729456"; public static final String APP_NOT_DISABLED = "b/139891609"; diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index 310d598b25..66fdc9467f 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -25,6 +25,7 @@ import static com.android.launcher3.LauncherState.NORMAL; import android.graphics.PointF; import android.graphics.Rect; +import android.util.Log; import android.view.GestureDetector; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; @@ -36,8 +37,10 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; +import com.android.launcher3.Utilities; import com.android.launcher3.Workspace; import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.OptionsPopupView; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -175,6 +178,9 @@ public class WorkspaceTouchListener extends GestureDetector.SimpleOnGestureListe mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, Action.Direction.NONE, ContainerType.WORKSPACE, mWorkspace.getCurrentPage()); + if (Utilities.IS_RUNNING_IN_TEST_HARNESS) { + Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on long press"); + } OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y); } else { cancelLongPress(); From 44fa2940cd8b055e94dd6e6e6bb60df2eb79c48a Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Fri, 29 Nov 2019 21:47:29 -0800 Subject: [PATCH 16/30] Render user's actual workspace in ThemePicker preview (Part 1) This change takes care of icon rendering. Further work still needs to be done for folders and widgets. Test: Go to grid options, choose a different grid option, and see user's workspace rendered in the preview Bug: 144052839 Change-Id: I696153dec1d1f08c5ac82d0c75be5740325c0fc4 --- .../launcher3/config/FeatureFlags.java | 4 + .../graphics/LauncherPreviewRenderer.java | 120 +++++++++++++++--- .../launcher3/model/BaseLoaderResults.java | 95 +------------- .../android/launcher3/model/LoaderTask.java | 2 +- .../android/launcher3/model/ModelUtils.java | 112 ++++++++++++++++ 5 files changed, 227 insertions(+), 106 deletions(-) create mode 100644 src/com/android/launcher3/model/ModelUtils.java diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index ae30380c78..1ae30b7d8f 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -126,6 +126,10 @@ public final class FeatureFlags { public static final TogglableFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = new TogglableFlag( "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache"); + public static final TogglableFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = new TogglableFlag( + "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", false, + "Show launcher preview in grid picker"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 2badb6e2f3..0c5535ffe0 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -19,6 +19,10 @@ import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.View.VISIBLE; +import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER; +import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; + import android.annotation.TargetApi; import android.app.Fragment; import android.content.Context; @@ -48,6 +52,10 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Hotseat; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -58,11 +66,20 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.BitmapRenderer; +import com.android.launcher3.model.AllAppsList; +import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.LoaderResults; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import java.util.ArrayList; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. @@ -241,22 +258,59 @@ public class LauncherPreviewRenderer implements Callable { } private void renderScreenShot(Canvas canvas) { - // Add hotseat icons - for (int i = 0; i < mIdp.numHotseatIcons; i++) { - WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); - info.container = Favorites.CONTAINER_HOTSEAT; - info.screenId = i; - inflateAndAddIcon(info); - } + if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) { + final LauncherModel launcherModel = LauncherAppState.getInstance( + mContext).getModel(); + final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher(); + launcherModel.enqueueModelUpdateTask(fetcher); + ArrayList workspaceItems; + try { + workspaceItems = fetcher.mTask.get(5, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Log.d(TAG, "Error fetching workspace items info", e); + return; + } - // Add workspace icons - for (int i = 0; i < mIdp.numColumns; i++) { - WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); - info.container = Favorites.CONTAINER_DESKTOP; - info.screenId = 0; - info.cellX = i; - info.cellY = mIdp.numRows - 1; - inflateAndAddIcon(info); + // Separate the items that are on the current screen, and all the other remaining + // items + ArrayList currentWorkspaceItems = new ArrayList<>(); + ArrayList otherWorkspaceItems = new ArrayList<>(); + + filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceItems, + currentWorkspaceItems, otherWorkspaceItems); + sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems); + + for (ItemInfo itemInfo : currentWorkspaceItems) { + switch (itemInfo.itemType) { + case Favorites.ITEM_TYPE_APPLICATION: + case Favorites.ITEM_TYPE_SHORTCUT: + case Favorites.ITEM_TYPE_DEEP_SHORTCUT: + inflateAndAddIcon((WorkspaceItemInfo) itemInfo); + break; + case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: + // TODO: for folder implementation here. + break; + default: + break; + } + } + } else { + // Add hotseat icons + for (int i = 0; i < mIdp.numHotseatIcons; i++) { + WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); + info.container = Favorites.CONTAINER_HOTSEAT; + info.screenId = i; + inflateAndAddIcon(info); + } + // Add workspace icons + for (int i = 0; i < mIdp.numColumns; i++) { + WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo); + info.container = Favorites.CONTAINER_DESKTOP; + info.screenId = 0; + info.cellX = i; + info.cellY = mIdp.numRows - 1; + inflateAndAddIcon(info); + } } // Add first page QSB @@ -286,6 +340,42 @@ public class LauncherPreviewRenderer implements Callable { } } + private static class WorkspaceItemsInfoFetcher implements Callable>, + LauncherModel.ModelUpdateTask { + + private final FutureTask> mTask = new FutureTask<>(this); + + private LauncherAppState mApp; + private LauncherModel mModel; + private BgDataModel mBgDataModel; + private AllAppsList mAllAppsList; + + @Override + public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, + AllAppsList allAppsList, Executor uiExecutor) { + mApp = app; + mModel = model; + mBgDataModel = dataModel; + mAllAppsList = allAppsList; + } + + @Override + public void run() { + mTask.run(); + } + + @Override + public ArrayList call() throws Exception { + if (!mModel.isModelLoaded()) { + Log.d(TAG, "Workspace not loaded, loading now"); + mModel.startLoaderForResults( + new LoaderResults(mApp, mBgDataModel, mAllAppsList, 0, null)); + return new ArrayList<>(); + } + return mBgDataModel.workspaceItems; + } + } + private static void measureView(View view, int width, int height) { view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); view.layout(0, 0, width, height); diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java index a00a6bd3a5..76c2951310 100644 --- a/src/com/android/launcher3/model/BaseLoaderResults.java +++ b/src/com/android/launcher3/model/BaseLoaderResults.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; +import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.os.Looper; @@ -27,20 +29,15 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel.CallbackTask; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.PagedView; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.ViewOnDrawExecutor; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; import java.util.concurrent.Executor; /** @@ -123,8 +120,9 @@ public abstract class BaseLoaderResults { otherWorkspaceItems); filterCurrentWorkspaceItems(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets); - sortWorkspaceItemsSpatially(currentWorkspaceItems); - sortWorkspaceItemsSpatially(otherWorkspaceItems); + final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile(); + sortWorkspaceItemsSpatially(idp, currentWorkspaceItems); + sortWorkspaceItemsSpatially(idp, otherWorkspaceItems); // Tell the workspace that we're about to start binding items executeCallbacksTask(c -> { @@ -169,89 +167,6 @@ public abstract class BaseLoaderResults { } } - - /** Filters the set of items who are directly or indirectly (via another container) on the - * specified screen. */ - public static void filterCurrentWorkspaceItems(int currentScreenId, - ArrayList allWorkspaceItems, - ArrayList currentScreenItems, - ArrayList otherScreenItems) { - // Purge any null ItemInfos - Iterator iter = allWorkspaceItems.iterator(); - while (iter.hasNext()) { - ItemInfo i = iter.next(); - if (i == null) { - iter.remove(); - } - } - - // Order the set of items by their containers first, this allows use to walk through the - // list sequentially, build up a list of containers that are in the specified screen, - // as well as all items in those containers. - IntSet itemsOnScreen = new IntSet(); - Collections.sort(allWorkspaceItems, - (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); - - for (T info : allWorkspaceItems) { - if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (info.screenId == currentScreenId) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - if (itemsOnScreen.contains(info.container)) { - currentScreenItems.add(info); - itemsOnScreen.add(info.id); - } else { - otherScreenItems.add(info); - } - } - } - } - - /** Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to - * right) */ - protected void sortWorkspaceItemsSpatially(ArrayList workspaceItems) { - final InvariantDeviceProfile profile = mApp.getInvariantDeviceProfile(); - final int screenCols = profile.numColumns; - final int screenCellCount = profile.numColumns * profile.numRows; - Collections.sort(workspaceItems, new Comparator() { - @Override - public int compare(ItemInfo lhs, ItemInfo rhs) { - if (lhs.container == rhs.container) { - // Within containers, order by their spatial position in that container - switch (lhs.container) { - case LauncherSettings.Favorites.CONTAINER_DESKTOP: { - int lr = (lhs.screenId * screenCellCount + - lhs.cellY * screenCols + lhs.cellX); - int rr = (rhs.screenId * screenCellCount + - rhs.cellY * screenCols + rhs.cellX); - return Integer.compare(lr, rr); - } - case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { - // We currently use the screen id as the rank - return Integer.compare(lhs.screenId, rhs.screenId); - } - default: - if (FeatureFlags.IS_DOGFOOD_BUILD) { - throw new RuntimeException("Unexpected container type when " + - "sorting workspace items."); - } - return 0; - } - } else { - // Between containers, order by hotseat, desktop - return Integer.compare(lhs.container, rhs.container); - } - } - }); - } - protected void bindWorkspaceItems(final ArrayList workspaceItems, final Executor executor) { // Bind the workspace items diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 2754cfc6b8..cc994df82e 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,7 +19,7 @@ package com.android.launcher3.model; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; -import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; +import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import static com.android.launcher3.util.PackageManagerHelper.isSystemApp; diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java new file mode 100644 index 0000000000..628dd95c41 --- /dev/null +++ b/src/com/android/launcher3/model/ModelUtils.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2019 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.model; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.IntSet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; + +/** + * Utils class for {@link com.android.launcher3.LauncherModel}. + */ +public class ModelUtils { + + /** + * Filters the set of items who are directly or indirectly (via another container) on the + * specified screen. + */ + public static void filterCurrentWorkspaceItems(int currentScreenId, + ArrayList allWorkspaceItems, + ArrayList currentScreenItems, + ArrayList otherScreenItems) { + // Purge any null ItemInfos + Iterator iter = allWorkspaceItems.iterator(); + while (iter.hasNext()) { + ItemInfo i = iter.next(); + if (i == null) { + iter.remove(); + } + } + // Order the set of items by their containers first, this allows use to walk through the + // list sequentially, build up a list of containers that are in the specified screen, + // as well as all items in those containers. + IntSet itemsOnScreen = new IntSet(); + Collections.sort(allWorkspaceItems, + (lhs, rhs) -> Integer.compare(lhs.container, rhs.container)); + for (T info : allWorkspaceItems) { + if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (info.screenId == currentScreenId) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); + } + } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + if (itemsOnScreen.contains(info.container)) { + currentScreenItems.add(info); + itemsOnScreen.add(info.id); + } else { + otherScreenItems.add(info); + } + } + } + } + + /** + * Sorts the set of items by hotseat, workspace (spatially from top to bottom, left to right) + */ + public static void sortWorkspaceItemsSpatially(InvariantDeviceProfile profile, + ArrayList workspaceItems) { + final int screenCols = profile.numColumns; + final int screenCellCount = profile.numColumns * profile.numRows; + Collections.sort(workspaceItems, (lhs, rhs) -> { + if (lhs.container == rhs.container) { + // Within containers, order by their spatial position in that container + switch (lhs.container) { + case LauncherSettings.Favorites.CONTAINER_DESKTOP: { + int lr = (lhs.screenId * screenCellCount + lhs.cellY * screenCols + + lhs.cellX); + int rr = (rhs.screenId * screenCellCount + +rhs.cellY * screenCols + + rhs.cellX); + return Integer.compare(lr, rr); + } + case LauncherSettings.Favorites.CONTAINER_HOTSEAT: { + // We currently use the screen id as the rank + return Integer.compare(lhs.screenId, rhs.screenId); + } + default: + if (FeatureFlags.IS_DOGFOOD_BUILD) { + throw new RuntimeException( + "Unexpected container type when sorting workspace items."); + } + return 0; + } + } else { + // Between containers, order by hotseat, desktop + return Integer.compare(lhs.container, rhs.container); + } + }); + } +} From 80634893ab443d600c3c272b9b530a07f7d87914 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Mon, 9 Dec 2019 14:36:18 -0800 Subject: [PATCH 17/30] fix app crash on settings for launcher Bug: 145941320 Test: Settings -> Apps & notifications -> Default apps -> Gear button next to Home app -> Click on every item and make sure it doesn't crash Change-Id: Ibad2ebf0c53a58a04327d0323f83de27efd8fda8 --- .../launcher3/config/FlagTogglerPrefUi.java | 14 +++++++------- .../settings/DeveloperOptionsFragment.java | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java index 200938d27b..a7e3732397 100644 --- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java +++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java @@ -25,15 +25,15 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceGroup; +import androidx.preference.SwitchPreference; + import com.android.launcher3.R; import com.android.launcher3.config.FeatureFlags.BaseTogglableFlag; import com.android.launcher3.uioverrides.TogglableFlag; -import androidx.preference.PreferenceDataStore; -import androidx.preference.PreferenceFragment; -import androidx.preference.PreferenceGroup; -import androidx.preference.SwitchPreference; - /** * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}. */ @@ -41,7 +41,7 @@ public final class FlagTogglerPrefUi { private static final String TAG = "FlagTogglerPrefFrag"; - private final PreferenceFragment mFragment; + private final PreferenceFragmentCompat mFragment; private final Context mContext; private final SharedPreferences mSharedPreferences; @@ -72,7 +72,7 @@ public final class FlagTogglerPrefUi { } }; - public FlagTogglerPrefUi(PreferenceFragment fragment) { + public FlagTogglerPrefUi(PreferenceFragmentCompat fragment) { mFragment = fragment; mContext = fragment.getActivity(); mSharedPreferences = mContext.getSharedPreferences( diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java index 3668313bdc..049fda9372 100644 --- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java +++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java @@ -44,7 +44,7 @@ import android.view.View; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceDataStore; -import androidx.preference.PreferenceFragment; +import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; @@ -64,7 +64,7 @@ import java.util.stream.Collectors; * See {@link FeatureFlags}. */ @TargetApi(Build.VERSION_CODES.O) -public class DeveloperOptionsFragment extends PreferenceFragment { +public class DeveloperOptionsFragment extends PreferenceFragmentCompat { private static final String ACTION_PLUGIN_SETTINGS = "com.android.systemui.action.PLUGIN_SETTINGS"; private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN"; From e895869f479871b54432aaafd3080e374e5feab2 Mon Sep 17 00:00:00 2001 From: Zak Cohen Date: Mon, 2 Dec 2019 14:30:55 -0800 Subject: [PATCH 18/30] Overview actions - adds feature flag and base class for Overview Actions. Adds ENABLE_OVERVIEW_ACTIONS feature flag and base factory. Requires an implementation in overlay to show any actions. Test: run locally with flag on and off. Bug: 145628186 Change-Id: I1c36330464cc01e1e987ebfea1a9f451067598a5 --- .../uioverrides/states/OverviewState.java | 5 ++ .../quickstep/OverviewActionsFactory.java | 48 +++++++++++++++++++ .../android/quickstep/views/RecentsView.java | 4 +- .../com/android/quickstep/views/TaskView.java | 35 +++++++++++++- quickstep/res/layout/task.xml | 3 +- quickstep/res/values/config.xml | 2 + quickstep/res/values/dimens.xml | 6 ++- .../android/quickstep/util/LayoutUtils.java | 43 +++++++++++++---- .../launcher3/config/FeatureFlags.java | 3 ++ 9 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java index ed5dba1fd5..bd37e569bd 100644 --- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java +++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java @@ -44,6 +44,7 @@ import com.android.launcher3.R; import com.android.launcher3.Workspace; import com.android.launcher3.allapps.DiscoveryBounce; import com.android.launcher3.anim.AnimatorSetBuilder; +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.quickstep.SysUINavigationMode; @@ -140,6 +141,10 @@ public class OverviewState extends LauncherState { if (launcher.getDeviceProfile().isVerticalBarLayout()) { return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON; } else { + if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) { + return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON; + } + boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent(); return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON | diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java new file mode 100644 index 0000000000..6d17b27ab5 --- /dev/null +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewActionsFactory.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2019 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.quickstep; + +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; + +import android.view.View; + +import com.android.launcher3.R; +import com.android.launcher3.util.MainThreadInitializedObject; +import com.android.launcher3.util.ResourceBasedOverride; + +/** + * Overview actions are shown in overview underneath the task snapshot. This factory class is + * overrideable in an overlay. The {@link OverviewActions} class provides the view that should be + * shown in the Overview. + */ +public class OverviewActionsFactory implements ResourceBasedOverride { + + public static final MainThreadInitializedObject INSTANCE = + forOverride(OverviewActionsFactory.class, R.string.overview_actions_factory_class); + + /** Create a new Overview Actions for interacting between the actions and overview. */ + public OverviewActions createOverviewActions() { + return new OverviewActions(); + } + + /** Overlay overrideable, base class does nothing. */ + public static class OverviewActions { + /** Get the view to show in the overview. */ + public View getView() { + return null; + } + } +} diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java index b602cea4e7..a8d88b92ce 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java @@ -111,6 +111,7 @@ import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; import com.android.quickstep.ViewUtils; import com.android.quickstep.util.AppWindowAnimationHelper; +import com.android.quickstep.util.LayoutUtils; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -344,8 +345,7 @@ public abstract class RecentsView extends PagedView impl setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); mTaskTopMargin = getResources() .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); - mTaskBottomMargin = getResources().getDimensionPixelSize( - R.dimen.task_thumbnail_bottom_margin); + mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(getResources()); mSquaredTouchSlop = squaredTouchSlop(context); mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java index 0bfde64c37..94cec723b5 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java @@ -47,11 +47,14 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import android.widget.Toast; +import androidx.annotation.Nullable; + import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.Interpolators; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.popup.SystemShortcut; import com.android.launcher3.userevent.nano.LauncherLogProto; @@ -59,11 +62,13 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.PendingAnimation; import com.android.launcher3.util.ViewPool.Reusable; +import com.android.quickstep.OverviewActionsFactory; import com.android.quickstep.RecentsModel; import com.android.quickstep.TaskIconCache; import com.android.quickstep.TaskOverlayFactory; import com.android.quickstep.TaskThumbnailCache; import com.android.quickstep.TaskUtils; +import com.android.quickstep.util.LayoutUtils; import com.android.quickstep.util.TaskCornerRadius; import com.android.quickstep.views.RecentsView.PageCallbacks; import com.android.quickstep.views.RecentsView.ScrollState; @@ -159,6 +164,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { private final float mWindowCornerRadius; private final BaseDraggingActivity mActivity; + private OverviewActionsFactory.OverviewActions mOverviewActions; + @Nullable private View mActionsView; + private ObjectAnimator mIconAndDimAnimator; private float mIconScaleAnimStartProgress = 0; private float mFocusTransitionProgress = 1; @@ -214,6 +222,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius); mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this); + mOverviewActions = OverviewActionsFactory.INSTANCE.get(context).createOverviewActions(); mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams); setOutlineProvider(mOutlineProvider); } @@ -223,6 +232,21 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { super.onFinishInflate(); mSnapshotView = findViewById(R.id.snapshot); mIconView = findViewById(R.id.icon); + + TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams(); + thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getResources()); + mSnapshotView.setLayoutParams(thumbnailParams); + + + if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) { + mActionsView = mOverviewActions.getView(); + if (mActionsView != null) { + TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT, + getResources().getDimensionPixelSize(R.dimen.overview_actions_height), + Gravity.BOTTOM); + addView(mActionsView, params); + } + } } public TaskMenuView getMenuView() { @@ -422,6 +446,10 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mIconView.setScaleX(scale); mIconView.setScaleY(scale); + if (mActionsView != null) { + mActionsView.setAlpha(scale); + } + mFooterVerticalOffset = 1.0f - scale; for (FooterWrapper footer : mFooters) { if (footer != null) { @@ -626,7 +654,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) { mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); - mMarginBottom = res.getDimensionPixelSize(R.dimen.task_thumbnail_bottom_margin); + mMarginBottom = LayoutUtils.thumbnailBottomMargin(res); mFullscreenParams = fullscreenParams; } @@ -783,6 +811,7 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { /** * Hides the icon and shows insets when this TaskView is about to be shown fullscreen. + * * @param progress: 0 = show icon and no insets; 1 = don't show icon and show full insets. */ public void setFullscreenProgress(float progress) { @@ -793,6 +822,9 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mFullscreenProgress = progress; boolean isFullscreen = mFullscreenProgress > 0; mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); + if (mActionsView != null) { + mActionsView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE); + } setClipChildren(!isFullscreen); setClipToPadding(!isFullscreen); @@ -873,4 +905,5 @@ public class TaskView extends FrameLayout implements PageCallbacks, Reusable { mScale = scale; } } + } diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml index 7a36416c59..60cfa0cbaa 100644 --- a/quickstep/res/layout/task.xml +++ b/quickstep/res/layout/task.xml @@ -25,8 +25,7 @@ android:id="@+id/snapshot" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_marginTop="@dimen/task_thumbnail_top_margin" - android:layout_marginBottom="@dimen/task_thumbnail_bottom_margin"/> + android:layout_marginTop="@dimen/task_thumbnail_top_margin"/> + + diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 82833ea58c..9ff1350eff 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -18,12 +18,13 @@ 24dp 12dp - - 0dp 48dp 2dp + + 0dp + 10dp 70dp 96dp @@ -58,6 +59,7 @@ 3dp 0dp 136dp + 24dp 200dp 100dp - - - - diff --git a/go/quickstep/res/drawable/default_thumbnail.xml b/go/quickstep/res/drawable/default_thumbnail.xml deleted file mode 100644 index ab22dcf827..0000000000 --- a/go/quickstep/res/drawable/default_thumbnail.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - diff --git a/go/quickstep/res/drawable/empty_content_box.xml b/go/quickstep/res/drawable/empty_content_box.xml deleted file mode 100644 index a4883889fe..0000000000 --- a/go/quickstep/res/drawable/empty_content_box.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/go/quickstep/res/layout/clear_all_button.xml b/go/quickstep/res/layout/clear_all_button.xml deleted file mode 100644 index eef66add45..0000000000 --- a/go/quickstep/res/layout/clear_all_button.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - -