diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml index 5318a12edd..8db875be90 100644 --- a/AndroidManifest-common.xml +++ b/AndroidManifest-common.xml @@ -43,6 +43,7 @@ + 48dp 55dp + 55dp 28dp diff --git a/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java new file mode 100644 index 0000000000..853a1c6acb --- /dev/null +++ b/quickstep/src/com/android/launcher3/uioverrides/TogglableFlag.java @@ -0,0 +1,49 @@ +/* + * 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.uioverrides; + +import android.content.Context; +import android.provider.DeviceConfig; +import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; + +public class TogglableFlag extends BaseTogglableFlag { + public static final String NAMESPACE_LAUNCHER = "launcher"; + public static final String TAG = "TogglableFlag"; + + public TogglableFlag(String key, boolean defaultValue, String description) { + super(key, defaultValue, description); + } + + @Override + public boolean getOverridenDefaultValue(boolean value) { + return DeviceConfig.getBoolean(NAMESPACE_LAUNCHER, getKey(), value); + } + + @Override + public void addChangeListener(Context context, Runnable r) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_LAUNCHER, + context.getMainExecutor(), + (properties) -> { + if (!NAMESPACE_LAUNCHER.equals(properties.getNamespace())) { + return; + } + initialize(context); + r.run(); + }); + } +} diff --git a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java index 97cd38a117..c02df9386c 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +++ b/quickstep/src/com/android/launcher3/uioverrides/UiFactory.java @@ -32,9 +32,11 @@ import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.Activity; +import android.app.Person; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.CancellationSignal; import android.util.Base64; @@ -244,4 +246,9 @@ public class UiFactory extends RecentsUiFactory { } return new ScaleAndTranslation(1.1f, 0f, 0f); } + + public static Person[] getPersons(ShortcutInfo si) { + Person[] persons = si.getPersons(); + return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons; + } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java index 0605953dce..bb72315d3b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java @@ -46,7 +46,7 @@ public class LandscapeEdgeSwipeController extends AbstractStateChangeTouchContro } @Override - protected int getLogContainerTypeForNormalState() { + protected int getLogContainerTypeForNormalState(MotionEvent ev) { return LauncherLogProto.ContainerType.NAVBAR; } diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java index a55f36b787..b81edfa4ca 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java @@ -147,8 +147,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr } @Override - protected int getLogContainerTypeForNormalState() { - return ContainerType.HOTSEAT; + protected int getLogContainerTypeForNormalState(MotionEvent ev) { + return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE; } private AnimatorSetBuilder getNormalToOverviewAnimation() { diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java index 9f12484589..dfab43459d 100644 --- a/quickstep/src/com/android/quickstep/RecentsModel.java +++ b/quickstep/src/com/android/quickstep/RecentsModel.java @@ -16,15 +16,12 @@ package com.android.quickstep; import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; -import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS; -import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_WINDOW_CORNER_RADIUS; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.ComponentCallbacks2; import android.content.Context; import android.os.Build; -import android.os.Bundle; import android.os.HandlerThread; import android.os.Process; import android.os.RemoteException; @@ -35,7 +32,6 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.recents.model.Task; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; import java.util.ArrayList; @@ -52,7 +48,7 @@ public class RecentsModel extends TaskStackChangeListener { // We do not need any synchronization for this variable as its only written on UI thread. public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(c -> new RecentsModel(c)); + new MainThreadInitializedObject<>(RecentsModel::new); private final List mThumbnailChangeListeners = new ArrayList<>(); private final Context mContext; diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java index 2f411efc7b..bf3cd8afe2 100644 --- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java +++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java @@ -16,18 +16,19 @@ package com.android.quickstep.logging; -import android.content.Context; -import android.content.Intent; -import android.stats.launcher.nano.LauncherExtension; -import android.stats.launcher.nano.LauncherTarget; - import static android.stats.launcher.nano.Launcher.ALLAPPS; import static android.stats.launcher.nano.Launcher.HOME; import static android.stats.launcher.nano.Launcher.LAUNCH_APP; import static android.stats.launcher.nano.Launcher.LAUNCH_TASK; +import static android.stats.launcher.nano.Launcher.DISMISS_TASK; import static android.stats.launcher.nano.Launcher.BACKGROUND; import static android.stats.launcher.nano.Launcher.OVERVIEW; +import android.content.Context; +import android.content.Intent; +import android.stats.launcher.nano.Launcher; +import android.stats.launcher.nano.LauncherExtension; +import android.stats.launcher.nano.LauncherTarget; import android.view.View; import com.android.launcher3.ItemInfo; @@ -38,8 +39,6 @@ import com.android.launcher3.util.ComponentKey; import com.android.systemui.shared.system.StatsLogCompat; import com.google.protobuf.nano.MessageNano; -import androidx.annotation.Nullable; - /** * This method calls the StatsLog hidden method until they are made available public. * @@ -74,6 +73,27 @@ public class StatsLogCompatManager extends StatsLogManager { MessageNano.toByteArray(ext), true); } + @Override + public void logTaskDismiss(View v, ComponentKey componentKey) { + LauncherExtension ext = new LauncherExtension(); + ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH]; + int srcState = OVERVIEW; + fillInLauncherExtension(v, ext); + StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */, + MessageNano.toByteArray(ext), true); + } + + @Override + public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { + LauncherExtension ext = new LauncherExtension(); + ext.srcTarget = new LauncherTarget[1]; + int srcState = mStateProvider.getCurrentState(); + fillInLauncherExtensionWithPageId(ext, pageId); + int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT; + StatsLogCompat.write(launcherAction, srcState, srcState, + MessageNano.toByteArray(ext), true); + } + public static boolean fillInLauncherExtension(View v, LauncherExtension extension) { StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v); if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) { @@ -88,6 +108,13 @@ public class StatsLogCompatManager extends StatsLogManager { return true; } + public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) { + Target target = new Target(); + target.pageIndex = pageId; + copy(target, ext.srcTarget[0]); + return true; + } + private static void copy(Target src, LauncherTarget dst) { // fill in } diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java index 3747f9a8b1..dc6b56eecf 100644 --- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java +++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java @@ -156,12 +156,14 @@ public class ShelfScrimView extends ScrimView implements NavigationModeChangeLis mDragHandleProgress = 1; mMidAlpha = 0; } else { - mMidAlpha = Themes.getAttrInteger(getContext(), R.attr.allAppsInterimScrimAlpha); + Context context = getContext(); + mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha); mMidProgress = OVERVIEW.getVerticalProgress(mLauncher); Rect hotseatPadding = dp.getHotseatLayoutPadding(); int hotseatSize = dp.hotseatBarSizePx + dp.getInsets().bottom - hotseatPadding.bottom - hotseatPadding.top; - float dragHandleTop = Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(dp)); + float dragHandleTop = + Math.min(hotseatSize, OverviewState.getDefaultSwipeHeight(context, dp)); mDragHandleProgress = 1 - (dragHandleTop / mShiftRange); } mTopOffset = dp.getInsets().top - mShelfOffset; diff --git a/quickstep/tests/OWNERS b/quickstep/tests/OWNERS index 046d871163..02e8ebcaba 100644 --- a/quickstep/tests/OWNERS +++ b/quickstep/tests/OWNERS @@ -1 +1,4 @@ vadimt@google.com +sunnygoyal@google.com +winsonc@google.com +hyunyoungs@google.com diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java index ec3d49afad..a7c33a9549 100644 --- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java +++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java @@ -69,10 +69,9 @@ public class DigitalWellBeingToastTest extends AbstractQuickStepTest { private DigitalWellBeingToast getToast() { executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW)); waitForState("Launcher internal state didn't switch to Overview", OVERVIEW); - waitForLauncherCondition("No latest task", launcher -> getLatestTask(launcher) != null); + final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher)); return getFromLauncher(launcher -> { - final TaskView task = getLatestTask(launcher); assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(task.getTask().getTopComponent().getPackageName())); return task.getDigitalWellBeingToast(); diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java index f27f400884..e29552713d 100644 --- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java +++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java @@ -34,9 +34,9 @@ import androidx.test.uiautomator.UiDevice; import com.android.launcher3.tapl.LauncherInstrumentation; import com.android.launcher3.tapl.TestHelpers; +import com.android.launcher3.util.rule.FailureWatcher; import com.android.systemui.shared.system.QuickStepContract; -import org.junit.Assert; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -79,6 +79,14 @@ public class NavigationModeSwitchRule implements TestRule { description.getAnnotation(NavigationModeSwitch.class) != null) { Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode(); return new Statement() { + private void assertTrue(String message, boolean condition) { + if(!condition) { + final AssertionError assertionError = new AssertionError(message); + FailureWatcher.onError(mLauncher.getDevice(), description, assertionError); + throw assertionError; + } + } + @Override public void evaluate() throws Throwable { mLauncher.enableDebugTracing(); @@ -107,9 +115,9 @@ public class NavigationModeSwitchRule implements TestRule { Log.e(TAG, "Exception", e); throw e; } finally { - Assert.assertTrue(setActiveOverlay(prevOverlayPkg, originalMode)); + assertTrue("Couldn't set overlay", + setActiveOverlay(prevOverlayPkg, originalMode)); } - mLauncher.disableDebugTracing(); } private void evaluateWithThreeButtons() throws Throwable { @@ -176,7 +184,7 @@ public class NavigationModeSwitchRule implements TestRule { latch.await(10, TimeUnit.SECONDS); targetContext.getMainExecutor().execute(() -> sysUINavigationMode.removeModeChangeListener(listener)); - Assert.assertTrue("Navigation mode didn't change to " + expectedMode, + assertTrue("Navigation mode didn't change to " + expectedMode, currentSysUiNavigationMode() == expectedMode); } @@ -184,7 +192,7 @@ public class NavigationModeSwitchRule implements TestRule { if (mLauncher.getNavigationModel() == expectedMode) break; Thread.sleep(100); } - Assert.assertTrue("Couldn't switch to " + overlayPackage, + assertTrue("Couldn't switch to " + overlayPackage, mLauncher.getNavigationModel() == expectedMode); for (int i = 0; i != 100; ++i) { @@ -192,7 +200,7 @@ public class NavigationModeSwitchRule implements TestRule { Thread.sleep(100); } final String error = mLauncher.getNavigationModeMismatchError(); - Assert.assertTrue("Switching nav mode: " + error, error == null); + assertTrue("Switching nav mode: " + error, error == null); Thread.sleep(5000); return true; diff --git a/res/values/config.xml b/res/values/config.xml index 638a411be6..038718473b 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -72,6 +72,7 @@ + diff --git a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java index 92bcc64348..a3d121676f 100644 --- a/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java +++ b/robolectric_tests/src/com/android/launcher3/config/FlagOverrideRule.java @@ -1,6 +1,8 @@ package com.android.launcher3.config; +import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; +import com.android.launcher3.uioverrides.TogglableFlag; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; @@ -70,7 +72,7 @@ public final class FlagOverrideRule implements TestRule { }; } - private void override(BaseFlags.TogglableFlag flag, boolean newValue) { + private void override(BaseTogglableFlag flag, boolean newValue) { if (!ruleInProgress) { throw new IllegalStateException( "Rule isn't in progress. Did you remember to mark it with @Rule?"); @@ -93,7 +95,7 @@ public final class FlagOverrideRule implements TestRule { private void applyAnnotation(FlagOverride flagOverride) { boolean found = false; - for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) { + for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { if (flag.getKey().equals(flagOverride.key())) { override(flag, flagOverride.value()); found = true; @@ -109,7 +111,7 @@ public final class FlagOverrideRule implements TestRule { * Resets all flags to their default values. */ private void clearOverrides() { - for (BaseFlags.TogglableFlag flag : FeatureFlags.getTogglableFlags()) { + for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) { flag.setForTests(flag.getDefaultValue()); } } diff --git a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java index e9324f9ce2..42a4f5cc99 100644 --- a/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java +++ b/robolectric_tests/src/com/android/launcher3/model/PackageInstallStateChangedTaskTest.java @@ -29,7 +29,8 @@ public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestC private PackageInstallStateChangedTask newTask(String pkg, int progress) { int state = PackageInstallerCompat.STATUS_INSTALLING; - PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress); + PackageInstallInfo installInfo = new PackageInstallInfo(pkg, state, progress, + android.os.Process.myUserHandle()); return new PackageInstallStateChangedTask(installInfo); } diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 733f29540f..8b49c06389 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -89,7 +89,7 @@ public class AllAppsList { public void addPromiseApp(Context context, PackageInstallerCompat.PackageInstallInfo installInfo) { ApplicationInfo applicationInfo = LauncherAppsCompat.getInstance(context) - .getApplicationInfo(installInfo.packageName, 0, Process.myUserHandle()); + .getApplicationInfo(installInfo.packageName, 0, installInfo.user); // only if not yet installed if (applicationInfo == null) { PromiseAppInfo info = new PromiseAppInfo(installInfo); diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java index c84be4dacd..864fa6e8a6 100644 --- a/src/com/android/launcher3/BaseRecyclerView.java +++ b/src/com/android/launcher3/BaseRecyclerView.java @@ -22,6 +22,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.views.RecyclerViewFastScroller; import androidx.recyclerview.widget.RecyclerView; @@ -171,4 +172,13 @@ public abstract class BaseRecyclerView extends RecyclerView { *

Override in each subclass of this base class. */ public void onFastScrollCompleted() {} + + @Override + public void onScrollStateChanged(int state) { + super.onScrollStateChanged(state); + + if (state == SCROLL_STATE_IDLE) { + AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext()); + } + } } \ No newline at end of file diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 22c69f59a5..b1132494a2 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -32,7 +32,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.text.TextUtils.TruncateAt; import android.util.AttributeSet; -import android.util.Log; import android.util.Property; import android.util.TypedValue; import android.view.KeyEvent; @@ -54,8 +53,8 @@ import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.icons.IconCache.IconLoadRequest; import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver; import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.IconLabelDotView; import java.text.NumberFormat; @@ -64,7 +63,8 @@ import java.text.NumberFormat; * because we want to make the bubble taller than the text and TextView's clip is * too aggressive. */ -public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback { +public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback, + IconLabelDotView { private static final int DISPLAY_WORKSPACE = 0; private static final int DISPLAY_ALL_APPS = 1; @@ -413,7 +413,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } - public void forceHideDot(boolean forceHideDot) { + @Override + public void setForceHideDot(boolean forceHideDot) { if (mForceHideDot == forceHideDot) { return; } @@ -602,6 +603,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } + @Override public void setIconVisible(boolean visible) { mIsIconVisible = visible; Drawable icon = visible ? mIcon : new ColorDrawable(Color.TRANSPARENT); diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 7ab88a0083..a90025e97f 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -142,8 +142,11 @@ public class FastBitmapDrawable extends Drawable { @Override public void setAlpha(int alpha) { - mAlpha = alpha; - mPaint.setAlpha(alpha); + if (mAlpha != alpha) { + mAlpha = alpha; + mPaint.setAlpha(alpha); + invalidateSelf(); + } } @Override diff --git a/src/com/android/launcher3/IconProvider.java b/src/com/android/launcher3/IconProvider.java index e1ef9548cd..0f006f7c19 100644 --- a/src/com/android/launcher3/IconProvider.java +++ b/src/com/android/launcher3/IconProvider.java @@ -1,16 +1,17 @@ package com.android.launcher3; -import android.content.Context; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; + import android.content.pm.LauncherActivityInfo; import android.graphics.drawable.Drawable; +import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; public class IconProvider implements ResourceBasedOverride { - public static IconProvider newInstance(Context context) { - return Overrides.getObject(IconProvider.class, context, R.string.icon_provider_class); - } + public static MainThreadInitializedObject INSTANCE = + forOverride(IconProvider.class, R.string.icon_provider_class); public IconProvider() { } diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index e9b932aaf5..f19c602685 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -19,6 +19,7 @@ package com.android.launcher3; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -29,7 +30,6 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.Parcelable; import android.os.Process; @@ -141,7 +141,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { String pkg = getIntentPackage(info.launchIntent); if (!TextUtils.isEmpty(pkg) - && !launcherApps.isPackageEnabledForProfile(pkg, info.user)) { + && !launcherApps.isPackageEnabledForProfile(pkg, info.user) + && !info.isActivity) { if (DBG) Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent); continue; @@ -250,7 +251,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } public static WorkspaceItemInfo fromActivityInfo(LauncherActivityInfo info, Context context) { - return (WorkspaceItemInfo) (new PendingInstallShortcutInfo(info, context).getItemInfo().first); + return (WorkspaceItemInfo) + new PendingInstallShortcutInfo(info, context).getItemInfo().first; } public static void queueShortcut(ShortcutInfo info, Context context) { @@ -261,8 +263,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context); } - public static void queueActivityInfo(LauncherActivityInfo activity, Context context) { - queuePendingShortcutInfo(new PendingInstallShortcutInfo(activity, context), context); + public static void queueApplication(Intent data, UserHandle user, Context context) { + queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user), + context); } public static HashSet getPendingShortcuts(Context context) { @@ -326,7 +329,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { private static class PendingInstallShortcutInfo { - final LauncherActivityInfo activityInfo; + final boolean isActivity; final ShortcutInfo shortcutInfo; final AppWidgetProviderInfo providerInfo; @@ -340,7 +343,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { * Initializes a PendingInstallShortcutInfo received from a different app. */ public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) { - activityInfo = null; + isActivity = false; shortcutInfo = null; providerInfo = null; @@ -350,14 +353,13 @@ public class InstallShortcutReceiver extends BroadcastReceiver { launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - } /** * Initializes a PendingInstallShortcutInfo to represent a launcher target. */ public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) { - activityInfo = info; + isActivity = true; shortcutInfo = null; providerInfo = null; @@ -369,11 +371,27 @@ public class InstallShortcutReceiver extends BroadcastReceiver { label = info.getLabel().toString(); } + /** + * Initializes a PendingInstallShortcutInfo to represent a launcher target. + */ + public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) { + isActivity = true; + shortcutInfo = null; + providerInfo = null; + + this.data = data; + this.user = user; + mContext = context; + + launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + } + /** * Initializes a PendingInstallShortcutInfo to represent a launcher target. */ public PendingInstallShortcutInfo(ShortcutInfo info, Context context) { - activityInfo = null; + isActivity = false; shortcutInfo = info; providerInfo = null; @@ -390,7 +408,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { */ public PendingInstallShortcutInfo( AppWidgetProviderInfo info, int widgetId, Context context) { - activityInfo = null; + isActivity = false; shortcutInfo = null; providerInfo = info; @@ -405,17 +423,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { public String encodeToString() { try { - if (activityInfo != null) { - // If it a launcher target, we only need component name, and user to - // recreate this. - return new JSONStringer() - .object() - .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) - .key(APP_SHORTCUT_TYPE_KEY).value(true) - .key(USER_HANDLE_KEY).value(UserManagerCompat.getInstance(mContext) - .getSerialNumberForUser(user)) - .endObject().toString(); - } else if (shortcutInfo != null) { + if (shortcutInfo != null) { // If it a launcher target, we only need component name, and user to // recreate this. return new JSONStringer() @@ -457,7 +465,8 @@ public class InstallShortcutReceiver extends BroadcastReceiver { JSONStringer json = new JSONStringer() .object() .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0)) - .key(NAME_KEY).value(name); + .key(NAME_KEY).value(name) + .key(APP_SHORTCUT_TYPE_KEY).value(isActivity); if (icon != null) { byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon); json = json.key(ICON_KEY).value( @@ -477,29 +486,18 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } public Pair getItemInfo() { - if (activityInfo != null) { - AppInfo appInfo = new AppInfo(mContext, activityInfo, user); - final LauncherAppState app = LauncherAppState.getInstance(mContext); - // Set default values until proper values is loaded. - appInfo.title = ""; - appInfo.applyFrom(app.getIconCache().getDefaultIcon(user)); - final WorkspaceItemInfo si = appInfo.makeWorkspaceItem(); - if (Looper.myLooper() == LauncherModel.getWorkerLooper()) { - app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */); - } else { - app.getModel().updateAndBindWorkspaceItem(() -> { - app.getIconCache().getTitleAndIcon( - si, activityInfo, false /* useLowResIcon */); - return si; - }); - } - return Pair.create((ItemInfo) si, (Object) activityInfo); + if (isActivity) { + WorkspaceItemInfo si = createWorkspaceItemInfo(data, + LauncherAppState.getInstance(mContext)); + si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; + return Pair.create(si, null); } else if (shortcutInfo != null) { - WorkspaceItemInfo si = new WorkspaceItemInfo(shortcutInfo, mContext); + WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext); LauncherIcons li = LauncherIcons.obtain(mContext); - si.applyFrom(li.createShortcutIcon(shortcutInfo)); + itemInfo.applyFrom(li.createShortcutIcon(shortcutInfo)); li.recycle(); - return Pair.create((ItemInfo) si, (Object) shortcutInfo); + return Pair.create(itemInfo, shortcutInfo); } else if (providerInfo != null) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo .fromProviderInfo(mContext, providerInfo); @@ -511,15 +509,16 @@ public class InstallShortcutReceiver extends BroadcastReceiver { widgetInfo.minSpanY = info.minSpanY; widgetInfo.spanX = Math.min(info.spanX, idp.numColumns); widgetInfo.spanY = Math.min(info.spanY, idp.numRows); - return Pair.create((ItemInfo) widgetInfo, (Object) providerInfo); + return Pair.create(widgetInfo, providerInfo); } else { - WorkspaceItemInfo si = createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext)); - return Pair.create((ItemInfo) si, null); + WorkspaceItemInfo itemInfo = + createWorkspaceItemInfo(data, LauncherAppState.getInstance(mContext)); + return Pair.create(itemInfo, null); } } public boolean isLauncherActivity() { - return activityInfo != null; + return isActivity; } } @@ -534,7 +533,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver { if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) { LauncherActivityInfo info = LauncherAppsCompat.getInstance(context) .resolveActivity(decoder.launcherIntent, decoder.user); - return info == null ? null : new PendingInstallShortcutInfo(info, context); + if (info != null) { + return new PendingInstallShortcutInfo(info, context); + } } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) { DeepShortcutManager sm = DeepShortcutManager.getInstance(context); List si = sm.queryForFullDetails( @@ -578,7 +579,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); } - return new PendingInstallShortcutInfo(data, decoder.user, context); + if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) { + return new PendingInstallShortcutInfo(data, context, decoder.user); + } else { + return new PendingInstallShortcutInfo(data, decoder.user, context); + } } catch (JSONException | URISyntaxException e) { Log.d(TAG, "Exception reading shortcut to add: " + e); } @@ -626,6 +631,11 @@ public class InstallShortcutReceiver extends BroadcastReceiver { } private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, LauncherAppState app) { + if (data == null) { + Log.e(TAG, "Can't construct WorkspaceItemInfo with null data"); + return null; + } + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java index 134e116063..3f723d17f1 100644 --- a/src/com/android/launcher3/ItemInfo.java +++ b/src/com/android/launcher3/ItemInfo.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.os.Process; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.util.ContentWriter; /** @@ -134,6 +136,7 @@ public class ItemInfo { return null; } + @Nullable public ComponentName getTargetComponent() { Intent intent = getIntent(); if (intent != null) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 5b38261a4e..257f0dfcf3 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -127,6 +127,7 @@ import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.PendingRequestArgs; import com.android.launcher3.util.RaceConditionTracker; +import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.Themes; import com.android.launcher3.util.Thunk; @@ -156,6 +157,7 @@ import java.util.List; import java.util.function.Predicate; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; /** * Default launcher application. @@ -208,9 +210,9 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500; // How long to wait before the new-shortcut animation automatically pans the workspace - private static final int NEW_APPS_PAGE_MOVE_DELAY = 500; + @VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500; private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5; - @Thunk static final int NEW_APPS_ANIMATION_DELAY = 500; + @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500; private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1; private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0; @@ -873,9 +875,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, if (mLauncherCallbacks != null) { mLauncherCallbacks.onStop(); } - - getUserEventDispatcher().logActionCommand(Action.Command.STOP, - mStateManager.getState().containerType, -1); + logStopAndResume(Action.Command.STOP); mAppWidgetHost.setListenIfResumed(false); @@ -901,8 +901,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, private void handleDeferredResume() { if (hasBeenResumed() && !mStateManager.getState().disableInteraction) { - getUserEventDispatcher().logActionCommand(Action.Command.RESUME, - mStateManager.getState().containerType, -1); + logStopAndResume(Action.Command.RESUME); getUserEventDispatcher().startSession(); UiFactory.onLauncherStateOrResumeChanged(this); @@ -929,6 +928,17 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, } } + private void logStopAndResume(int command) { + int containerType = mStateManager.getState().containerType; + if (containerType == ContainerType.WORKSPACE && mWorkspace != null) { + getUserEventDispatcher().logActionCommand(command, + containerType, -1, mWorkspace.isOverlayShown() ? -1 : 0); + } else { + getUserEventDispatcher().logActionCommand(command, containerType, -1); + } + + } + protected void onStateSet(LauncherState state) { getAppWidgetHost().setResumed(state == LauncherState.NORMAL); if (mDeferredResumePending) { @@ -2494,7 +2504,7 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, KeyEvent.KEYCODE_O, KeyEvent.META_CTRL_ON)); } if (currentFocus.getTag() instanceof ItemInfo - && DeepShortcutManager.supportsShortcuts((ItemInfo) currentFocus.getTag())) { + && ShortcutUtil.supportsShortcuts((ItemInfo) currentFocus.getTag())) { shortcutInfos.add(new KeyboardShortcutInfo( getString(R.string.shortcuts_menu_with_notifications_description), KeyEvent.KEYCODE_S, KeyEvent.META_CTRL_ON)); diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index d07638a5c1..b4a2216c57 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -19,14 +19,12 @@ package com.android.launcher3; import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver; -import android.app.KeyguardManager; import android.content.ComponentName; import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; -import android.os.Process; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; @@ -46,7 +44,7 @@ public class LauncherAppState { // We do not need any synchronization for this variable as its only written on UI thread. private static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>((c) -> new LauncherAppState(c)); + new MainThreadInitializedObject<>(LauncherAppState::new); private final Context mContext; private final LauncherModel mModel; @@ -96,6 +94,7 @@ public class LauncherAppState { if (FeatureFlags.IS_DOGFOOD_BUILD) { filter.addAction(ACTION_FORCE_ROLOAD); } + FeatureFlags.APP_SEARCH_IMPROVEMENTS.addChangeListener(context, mModel::forceReload); mContext.registerReceiver(mModel, filter); UserManagerCompat.getInstance(mContext).enableAndResetCache(); diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index d79f5d5a94..a0414894b1 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -20,6 +20,7 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD; import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; @@ -51,6 +52,7 @@ import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Preconditions; @@ -211,6 +213,30 @@ public class LauncherModel extends BroadcastReceiver enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } + public void onSessionFailure(String packageName, UserHandle user) { + enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + final IntSparseArrayMap removedIds = new IntSparseArrayMap<>(); + synchronized (dataModel) { + for (ItemInfo info : dataModel.itemsIdMap) { + if (info instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) info).hasPromiseIconUi() + && user.equals(info.user) + && info.getIntent() != null + && TextUtils.equals(packageName, info.getIntent().getPackage())) { + removedIds.put(info.id, true /* remove */); + } + } + } + + if (!removedIds.isEmpty()) { + deleteAndBindComponentsRemoved(ItemInfoMatcher.ofItemIds(removedIds, false)); + } + } + }); + } + @Override public void onPackageRemoved(String packageName, UserHandle user) { onPackagesRemoved(user, packageName); diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java index d66e5813b9..63914b0eb5 100644 --- a/src/com/android/launcher3/LauncherStateManager.java +++ b/src/com/android/launcher3/LauncherStateManager.java @@ -227,6 +227,11 @@ public class LauncherStateManager { private void goToState(LauncherState state, boolean animated, long delay, final Runnable onCompleteRunnable) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "goToState: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); + } animated &= Utilities.areAnimationsEnabled(mLauncher); if (mLauncher.isInState(state)) { if (mConfig.mCurrentAnimation == null) { @@ -407,6 +412,11 @@ public class LauncherStateManager { mState.onStateDisabled(mLauncher); } mState = state; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.STABLE_STATE_MISMATCH, "onStateTransitionStart: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); + } mState.onStateEnabled(mLauncher); mLauncher.onStateSet(mState); @@ -426,6 +436,11 @@ public class LauncherStateManager { if (state != mCurrentStableState) { mLastStableState = state.getHistoryForState(mCurrentStableState); mCurrentStableState = state; + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.ALL_APPS_UPON_RECENTS, "onStateTransitionEnd: " + + state.getClass().getSimpleName() + + " @ " + Log.getStackTraceString(new Throwable())); + } } state.onStateTransitionEnd(mLauncher); diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java index d2b8d4e300..79d6ca7934 100644 --- a/src/com/android/launcher3/PagedView.java +++ b/src/com/android/launcher3/PagedView.java @@ -890,23 +890,7 @@ public abstract class PagedView extends ViewGrou mTotalMotionX = 0; mActivePointerId = ev.getPointerId(0); - /* - * If being flinged and user touches the screen, initiate drag; - * otherwise don't. mScroller.isFinished should be false when - * being flinged. - */ - final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos()); - final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3); - - if (finishedScrolling) { - mIsBeingDragged = false; - if (!mScroller.isFinished() && !mFreeScroll) { - setCurrentPage(getNextPage()); - pageEndTransition(); - } - } else { - mIsBeingDragged = true; - } + updateIsBeingDraggedOnTouchDown(); break; } @@ -929,6 +913,25 @@ public abstract class PagedView extends ViewGrou return mIsBeingDragged; } + /** + * If being flinged and user touches the screen, initiate drag; otherwise don't. + */ + private void updateIsBeingDraggedOnTouchDown() { + // mScroller.isFinished should be false when being flinged. + final int xDist = Math.abs(mScroller.getFinalPos() - mScroller.getCurrPos()); + final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3); + + if (finishedScrolling) { + mIsBeingDragged = false; + if (!mScroller.isFinished() && !mFreeScroll) { + setCurrentPage(getNextPage()); + pageEndTransition(); + } + } else { + mIsBeingDragged = true; + } + } + public boolean isHandlingTouch() { return mIsBeingDragged; } @@ -1085,7 +1088,9 @@ public abstract class PagedView extends ViewGrou if (mFreeScroll) { setCurrentPage(getNextPage()); } else if (wasFreeScroll) { - snapToPage(getNextPage()); + if (getScrollForPage(getNextPage()) != getScrollX()) { + snapToPage(getNextPage()); + } } } @@ -1104,6 +1109,8 @@ public abstract class PagedView extends ViewGrou switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: + updateIsBeingDraggedOnTouchDown(); + /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. @@ -1562,12 +1569,20 @@ public abstract class PagedView extends ViewGrou final boolean pagesFlipped = isPageOrderFlipped(); info.setScrollable(getPageCount() > 1); if (getCurrentPage() < getPageCount() - 1) { - info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD - : AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + info.addAction(pagesFlipped ? + AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD + : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); + info.addAction(mIsRtl ? + AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT + : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT); } if (getCurrentPage() > 0) { - info.addAction(pagesFlipped ? AccessibilityNodeInfo.ACTION_SCROLL_FORWARD - : AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + info.addAction(pagesFlipped ? + AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD + : AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); + info.addAction(mIsRtl ? + AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT + : AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT); } // Accessibility-wise, PagedView doesn't support long click, so disabling it. @@ -1607,8 +1622,21 @@ public abstract class PagedView extends ViewGrou if (pagesFlipped ? scrollRight() : scrollLeft()) { return true; } + } break; + case android.R.id.accessibilityActionPageRight: { + if (!mIsRtl) { + return scrollRight(); + } else { + return scrollLeft(); + } + } + case android.R.id.accessibilityActionPageLeft: { + if (!mIsRtl) { + return scrollLeft(); + } else { + return scrollRight(); + } } - break; } return false; } diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java index b0da6b9cd9..b4078ee0ea 100644 --- a/src/com/android/launcher3/SessionCommitReceiver.java +++ b/src/com/android/launcher3/SessionCommitReceiver.java @@ -18,28 +18,33 @@ package com.android.launcher3; import android.annotation.TargetApi; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; +import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; -import android.os.Process; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; import java.util.List; +import static com.android.launcher3.compat.PackageInstallerCompat.getUserHandle; + /** * BroadcastReceiver to handle session commit intent. */ @@ -66,15 +71,29 @@ public class SessionCommitReceiver extends BroadcastReceiver { SessionInfo info = intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION); UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); + PackageInstallerCompat packageInstallerCompat = PackageInstallerCompat.getInstance(context); - if (TextUtils.isEmpty(info.getAppPackageName()) || - info.getInstallReason() != PackageManager.INSTALL_REASON_USER) { + if (TextUtils.isEmpty(info.getAppPackageName()) + || info.getInstallReason() != PackageManager.INSTALL_REASON_USER + || packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) { + packageInstallerCompat.removePromiseIconId(info.getSessionId()); return; } queueAppIconAddition(context, info.getAppPackageName(), user); } + public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) { + String packageName = sessionInfo.getAppPackageName(); + List activities = LauncherAppsCompat.getInstance(context) + .getActivityList(packageName, getUserHandle(sessionInfo)); + if (activities == null || activities.isEmpty()) { + // Ensure application isn't already installed. + queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(), + sessionInfo.getAppIcon(), getUserHandle(sessionInfo)); + } + } + public static void queueAppIconAddition(Context context, String packageName, UserHandle user) { List activities = LauncherAppsCompat.getInstance(context) .getActivityList(packageName, user); @@ -82,7 +101,18 @@ public class SessionCommitReceiver extends BroadcastReceiver { // no activity found return; } - InstallShortcutReceiver.queueActivityInfo(activities.get(0), context); + queueAppIconAddition(context, packageName, activities.get(0).getLabel(), null, user); + } + + private static void queueAppIconAddition(Context context, String packageName, + CharSequence label, Bitmap icon, UserHandle user) { + Intent data = new Intent(); + data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent( + new ComponentName(packageName, "")).setPackage(packageName)); + data.putExtra(Intent.EXTRA_SHORTCUT_NAME, label); + data.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon); + + InstallShortcutReceiver.queueApplication(data, user, context); } public static boolean isEnabled(Context context) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index fc5cd8a888..ba122f9441 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -21,6 +21,7 @@ import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.ActivityManager; +import android.app.Person; import android.app.WallpaperManager; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -109,6 +110,9 @@ public final class Utilities { private static final Matrix sMatrix = new Matrix(); private static final Matrix sInverseMatrix = new Matrix(); + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + public static final Person[] EMPTY_PERSON_ARRAY = new Person[0]; + public static final boolean ATLEAST_Q = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; public static final boolean ATLEAST_P = @@ -728,7 +732,7 @@ public final class Utilities { int[] array = new int[tokenizer.countTokens()]; int count = 0; while (tokenizer.hasMoreTokens()) { - array[count] = Integer.parseInt(tokenizer.nextToken()); + array[count] = Integer.parseInt(tokenizer.nextToken().trim()); count++; } return array; diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 269a591115..6612662ea5 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -85,6 +85,7 @@ import com.android.launcher3.logging.UserEventDispatcher; import com.android.launcher3.pageindicators.WorkspacePageIndicator; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.WorkspaceTouchListener; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -420,6 +421,9 @@ public class Workspace extends PagedView } // Always enter the spring loaded mode + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Switching to SPRING_LOADED"); + } mLauncher.getStateManager().goToState(SPRING_LOADED); } @@ -1048,6 +1052,7 @@ public class Workspace extends PagedView if (!mOverlayShown) { mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, Action.Direction.LEFT, ContainerType.WORKSPACE, 0); + mLauncher.getStatsLogManager().logSwipeOnContainer(true, 0); } mOverlayShown = true; // Not announcing the overlay page for accessibility since it announces itself. @@ -1057,6 +1062,7 @@ public class Workspace extends PagedView if (!ued.isPreviousHomeGesture()) { mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE, Action.Direction.RIGHT, ContainerType.WORKSPACE, -1); + mLauncher.getStatsLogManager().logSwipeOnContainer(false, -1); } } else if (Float.compare(mOverlayTranslation, 0f) != 0) { // When arriving to 0 overscroll from non-zero overscroll, announce page for @@ -1741,6 +1747,9 @@ public class Workspace extends PagedView public void prepareAccessibilityDrop() { } public void onDrop(final DragObject d, DragOptions options) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDrop"); + } mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter); CellLayout dropTargetLayout = mDropToLayout; @@ -2418,6 +2427,9 @@ public class Workspace extends PagedView * to add an item to one of the workspace screens. */ private void onDropExternal(final int[] touchXY, final CellLayout cellLayout, DragObject d) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "Workspace.onDropExternal"); + } if (d.dragInfo instanceof PendingAddShortcutInfo) { WorkspaceItemInfo si = ((PendingAddShortcutInfo) d.dragInfo) .activityInfo.createWorkspaceItemInfo(); @@ -3251,6 +3263,10 @@ public class Workspace extends PagedView } } + public boolean isOverlayShown() { + return mOverlayShown; + } + void moveToDefaultScreen() { int page = DEFAULT_PAGE; if (!workspaceInModalState() && getNextPage() != page) { diff --git a/src/com/android/launcher3/WorkspaceItemInfo.java b/src/com/android/launcher3/WorkspaceItemInfo.java index 5a2373b996..050a8bef75 100644 --- a/src/com/android/launcher3/WorkspaceItemInfo.java +++ b/src/com/android/launcher3/WorkspaceItemInfo.java @@ -16,17 +16,23 @@ package com.android.launcher3; +import android.app.Person; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ShortcutInfo; import android.text.TextUtils; +import androidx.annotation.NonNull; + import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.icons.IconCache; import com.android.launcher3.shortcuts.ShortcutKey; +import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.ContentWriter; +import java.util.Arrays; + /** * Represents a launchable icon on the workspaces and in folders. */ @@ -44,24 +50,26 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { * The icon was added as an auto-install app, and is not ready to be used. This flag can't * be present along with {@link #FLAG_RESTORED_ICON}, and is set during default layout * parsing. + * + * OR this icon was added due to it being an active install session created by the user. */ - public static final int FLAG_AUTOINSTALL_ICON = 2; //0B10; + public static final int FLAG_AUTOINSTALL_ICON = 1 << 1; /** * The icon is being installed. If {@link #FLAG_RESTORED_ICON} or {@link #FLAG_AUTOINSTALL_ICON} * is set, then the icon is either being installed or is in a broken state. */ - public static final int FLAG_INSTALL_SESSION_ACTIVE = 4; // 0B100; + public static final int FLAG_INSTALL_SESSION_ACTIVE = 1 << 2; /** * Indicates that the widget restore has started. */ - public static final int FLAG_RESTORE_STARTED = 8; //0B1000; + public static final int FLAG_RESTORE_STARTED = 1 << 3; /** * Web UI supported. */ - public static final int FLAG_SUPPORTS_WEB_UI = 16; //0B10000; + public static final int FLAG_SUPPORTS_WEB_UI = 1 << 4; /** * The intent used to start the application. @@ -82,11 +90,18 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { public int status; + /** + * A set of person's Id associated with the WorkspaceItemInfo, this is only used if the item + * represents a deep shortcut. + */ + @NonNull private String[] personKeys = Utilities.EMPTY_STRING_ARRAY; + /** * The installation progress [0-100] of the package that this shortcut represents. */ private int mInstallProgress; + public WorkspaceItemInfo() { itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; } @@ -98,6 +113,7 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { iconResource = info.iconResource; status = info.status; mInstallProgress = info.mInstallProgress; + personKeys = info.personKeys.clone(); } /** TODO: Remove this. It's only called by ApplicationInfo.makeWorkspaceItem. */ @@ -175,6 +191,10 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { runtimeStatusFlags |= FLAG_DISABLED_BY_PUBLISHER; } disabledMessage = shortcutInfo.getDisabledMessage(); + + Person[] persons = UiFactory.getPersons(shortcutInfo); + personKeys = persons.length == 0 ? Utilities.EMPTY_STRING_ARRAY + : Arrays.stream(persons).map(Person::getKey).sorted().toArray(String[]::new); } /** Returns the WorkspaceItemInfo id associated with the deep shortcut. */ @@ -183,11 +203,16 @@ public class WorkspaceItemInfo extends ItemInfoWithIcon { getIntent().getStringExtra(ShortcutKey.EXTRA_SHORTCUT_ID) : null; } + @NonNull + public String[] getPersonKeys() { + return personKeys; + } + @Override public ComponentName getTargetComponent() { ComponentName cn = super.getTargetComponent(); if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT - || hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) { + || hasStatusFlag(FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON))) { // Legacy shortcuts and promise icons with web UI may not have a componentName but just // a packageName. In that case create a dummy componentName instead of adding additional // check everywhere. diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java index fd4df5247e..0c12c60a08 100644 --- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java @@ -1,5 +1,7 @@ package com.android.launcher3.accessibility; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; + import static com.android.launcher3.LauncherState.NORMAL; import android.app.AlertDialog; @@ -30,16 +32,17 @@ import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.PendingAddItemInfo; import com.android.launcher3.R; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Workspace; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.dragndrop.DragController.DragListener; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.folder.Folder; +import com.android.launcher3.keyboard.CustomActionsPopup; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.popup.PopupContainerWithArrow; -import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.LauncherAppWidgetHostView; @@ -115,7 +118,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme // If the request came from keyboard, do not add custom shortcuts as that is already // exposed as a direct shortcut - if (!fromKeyboard && DeepShortcutManager.supportsShortcuts(item)) { + if (!fromKeyboard && ShortcutUtil.supportsShortcuts(item)) { info.addAction(mActions.get(NotificationListener.getInstanceIfConnected() != null ? SHORTCUTS_AND_NOTIFICATIONS : DEEP_SHORTCUTS)); } @@ -163,6 +166,13 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate impleme } public boolean performAction(final View host, final ItemInfo item, int action) { + if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) { + CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host); + if (popup.canShow()) { + popup.show(); + return true; + } + } if (action == MOVE) { beginAccessibleDrag(host, item); } else if (action == ADD_TO_WORKSPACE) { diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index 4a2109e588..293b86722d 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -628,20 +628,4 @@ public class AllAppsContainerView extends SpringRelativeLayout implements DragSo return super.performAccessibilityAction(action, arguments); } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - final boolean result = super.dispatchTouchEvent(ev); - switch (ev.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - if (result) mAllAppsStore.enableDeferUpdates( - AllAppsStore.DEFER_UPDATES_USER_INTERACTION); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mAllAppsStore.disableDeferUpdates(AllAppsStore.DEFER_UPDATES_USER_INTERACTION); - break; - } - return result; - } } diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java index a0e9dc5d8e..f82e380f88 100644 --- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java +++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java @@ -26,13 +26,15 @@ import android.util.SparseIntArray; import android.view.MotionEvent; import android.view.View; +import androidx.recyclerview.widget.RecyclerView; + import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; -import com.android.launcher3.Utilities; +import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; @@ -41,8 +43,6 @@ import com.android.launcher3.views.RecyclerViewFastScroller; import java.util.List; -import androidx.recyclerview.widget.RecyclerView; - /** * A RecyclerView with custom fast scroll support for the all apps view. */ @@ -114,6 +114,13 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine if (mScrollbar != null) { mScrollbar.reattachThumbToScroll(); } + if (getLayoutManager() instanceof AppsGridLayoutManager) { + AppsGridLayoutManager layoutManager = (AppsGridLayoutManager) getLayoutManager(); + if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) { + // We are at the top, so don't scrollToPosition (would cause unnecessary relayout). + return; + } + } scrollToPosition(0); } @@ -420,13 +427,4 @@ public class AllAppsRecyclerView extends BaseRecyclerView implements LogContaine public boolean hasOverlappingRendering() { return false; } - - @Override - public void onScrollStateChanged(int state) { - super.onScrollStateChanged(state); - - if (state == SCROLL_STATE_IDLE) { - AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext()); - } - } } diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 267363fa7d..ca8dbebfc2 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -39,10 +39,8 @@ public class AllAppsStore { // Defer updates flag used to defer all apps updates to the next draw. public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0; - // Defer updates flag used to defer all apps updates while the user interacts with all apps. - public static final int DEFER_UPDATES_USER_INTERACTION = 1 << 1; // Defer updates flag used to defer all apps updates by a test's request. - public static final int DEFER_UPDATES_TEST = 1 << 2; + public static final int DEFER_UPDATES_TEST = 1 << 1; private PackageUserKey mTempKey = new PackageUserKey(null, null); private final HashMap mComponentToAppMap = new HashMap<>(); diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java index 5b3beeca7f..3836c9fdb4 100644 --- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java +++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java @@ -18,7 +18,6 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.util.FloatProperty; -import android.util.Log; import android.view.animation.Interpolator; import com.android.launcher3.DeviceProfile; @@ -32,7 +31,6 @@ import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.anim.SpringObjectAnimator; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ScrimView; diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java new file mode 100644 index 0000000000..0f34c1e97e --- /dev/null +++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java @@ -0,0 +1,229 @@ +/* + * 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.anim; + +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.content.Context; +import android.util.FloatProperty; + +import com.android.launcher3.util.DefaultDisplay; + +import androidx.annotation.FloatRange; +import androidx.dynamicanimation.animation.SpringForce; + +/** + * Utility class to build an object animator which follows the same path as a spring animation for + * an underdamped spring. + */ +public class SpringAnimationBuilder extends FloatProperty { + + private final T mTarget; + private final FloatProperty mProperty; + + private float mStartValue; + private float mEndValue; + private float mVelocity = 0; + + private float mStiffness = SpringForce.STIFFNESS_MEDIUM; + private float mDampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY; + private float mMinVisibleChange = 1; + + // Multiplier to the min visible change value for value threshold + private static final float THRESHOLD_MULTIPLIER = 0.65f; + + /** + * The spring equation is given as + * x = e^(-beta*t/2) * (a cos(gamma * t) + b sin(gamma * t) + * v = e^(-beta*t/2) * ((2 * a * gamma + beta * b) * sin(gamma * t) + * + (a * beta - 2 * b * gamma) * cos(gamma * t)) / 2 + * + * a = x(0) + * b = beta * x(0) / (2 * gamma) + v(0) / gamma + */ + private double beta; + private double gamma; + + private double a, b; + private double va, vb; + + // Threshold for velocity and value to determine when it's reasonable to assume that the spring + // is approximately at rest. + private double mValueThreshold; + private double mVelocityThreshold; + + private float mCurrentTime = 0; + + public SpringAnimationBuilder(T target, FloatProperty property) { + super("dynamic-spring-property"); + mTarget = target; + mProperty = property; + + mStartValue = mProperty.get(target); + } + + public SpringAnimationBuilder setEndValue(float value) { + mEndValue = value; + return this; + } + + public SpringAnimationBuilder setStartValue(float value) { + mStartValue = value; + return this; + } + + public SpringAnimationBuilder setValues(float... values) { + if (values.length > 1) { + mStartValue = values[0]; + mEndValue = values[values.length - 1]; + } else { + mEndValue = values[0]; + } + return this; + } + + public SpringAnimationBuilder setStiffness( + @FloatRange(from = 0.0, fromInclusive = false) float stiffness) { + if (stiffness <= 0) { + throw new IllegalArgumentException("Spring stiffness constant must be positive."); + } + mStiffness = stiffness; + return this; + } + + public SpringAnimationBuilder setDampingRatio( + @FloatRange(from = 0.0, to = 1.0, fromInclusive = false, toInclusive = false) + float dampingRatio) { + if (dampingRatio <= 0 || dampingRatio >= 1) { + throw new IllegalArgumentException("Damping ratio must be between 0 and 1"); + } + mDampingRatio = dampingRatio; + return this; + } + + public SpringAnimationBuilder setMinimumVisibleChange( + @FloatRange(from = 0.0, fromInclusive = false) float minimumVisibleChange) { + if (minimumVisibleChange <= 0) { + throw new IllegalArgumentException("Minimum visible change must be positive."); + } + mMinVisibleChange = minimumVisibleChange; + return this; + } + + public SpringAnimationBuilder setStartVelocity(float startVelocity) { + mVelocity = startVelocity; + return this; + } + + @Override + public void setValue(T object, float time) { + mCurrentTime = time; + mProperty.setValue( + object, (float) (exponentialComponent(time) * cosSinX(time)) + mEndValue); + } + + @Override + public Float get(T t) { + return mCurrentTime; + } + + public ObjectAnimator build(Context context) { + int singleFrameMs = DefaultDisplay.getSingleFrameMs(context); + double naturalFreq = Math.sqrt(mStiffness); + double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio); + + // All the calculations assume the stable position to be 0, shift the values accordingly. + beta = 2 * mDampingRatio * naturalFreq; + gamma = dampedFreq; + a = mStartValue - mEndValue; + b = beta * a / (2 * gamma) + mVelocity / gamma; + + va = a * beta / 2 - b * gamma; + vb = a * gamma + beta * b / 2; + + mValueThreshold = mMinVisibleChange * THRESHOLD_MULTIPLIER; + + // This multiplier is used to calculate the velocity threshold given a certain value + // threshold. The idea is that if it takes >= 1 frame to move the value threshold amount, + // then the velocity is a reasonable threshold. + mVelocityThreshold = mValueThreshold * 1000.0 / singleFrameMs; + + // Find the duration (in seconds) for the spring to reach equilibrium. + // equilibrium is reached when x = 0 + double duration = Math.atan2(-a, b) / gamma; + + // Keep moving ahead until the velocity reaches equilibrium. + double piByG = Math.PI / gamma; + while (duration < 0 || Math.abs(exponentialComponent(duration) * cosSinV(duration)) + >= mVelocityThreshold) { + duration += piByG; + } + + // Find the shortest time + double edgeTime = Math.max(0, duration - piByG / 2); + double minDiff = singleFrameMs / 2000.0; // Half frame time in seconds + + do { + if ((duration - edgeTime) < minDiff) { + break; + } + double mid = (edgeTime + duration) / 2; + if (isAtEquilibrium(mid)) { + duration = mid; + } else { + edgeTime = mid; + } + } while (true); + + + long durationMs = (long) (1000.0 * duration); + ObjectAnimator animator = ObjectAnimator.ofFloat(mTarget, this, 0, (float) duration); + animator.setDuration(durationMs).setInterpolator(Interpolators.LINEAR); + animator.addListener(new AnimationSuccessListener() { + @Override + public void onAnimationSuccess(Animator animator) { + mProperty.setValue(mTarget, mEndValue); + } + }); + return animator; + } + + private boolean isAtEquilibrium(double t) { + double ec = exponentialComponent(t); + + if (Math.abs(ec * cosSinX(t)) >= mValueThreshold) { + return false; + } + return Math.abs(ec * cosSinV(t)) < mVelocityThreshold; + } + + private double exponentialComponent(double t) { + return Math.pow(Math.E, - beta * t / 2); + } + + private double cosSinX(double t) { + return cosSin(t, a, b); + } + + private double cosSinV(double t) { + return cosSin(t, va, vb); + } + + private double cosSin(double t, double cosFactor, double sinFactor) { + double angle = t * gamma; + return cosFactor * Math.cos(angle) + sinFactor * Math.sin(angle); + } +} diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java index 1d19b533a0..1885d8f03d 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVL.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVL.java @@ -31,11 +31,14 @@ import android.os.Bundle; import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; +import android.util.Log; import com.android.launcher3.compat.ShortcutConfigActivityInfo.ShortcutConfigActivityInfoVL; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import androidx.annotation.NonNull; @@ -167,6 +170,10 @@ public class LauncherAppsCompatVL extends LauncherAppsCompat { @Override public void onPackagesSuspended(String[] packageNames, UserHandle user) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.APP_NOT_DISABLED, "onPackagesSuspended: " + + Arrays.toString(packageNames)); + } mCallback.onPackagesSuspended(packageNames, user); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompat.java b/src/com/android/launcher3/compat/PackageInstallerCompat.java index 4f4d64161a..55df98b48f 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompat.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompat.java @@ -19,15 +19,25 @@ package com.android.launcher3.compat; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageInstaller; +import android.content.pm.PackageInstaller.SessionInfo; +import android.os.Process; import android.os.UserHandle; +import java.util.Collections; import java.util.HashMap; import java.util.List; import androidx.annotation.NonNull; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.PackageUserKey; + public abstract class PackageInstallerCompat { + // Set of session ids of promise icons that have been added to the home screen + // as FLAG_PROMISE_NEW_INSTALLS. + protected static final String PROMISE_ICON_IDS = "promise_icon_ids"; + public static final int STATUS_INSTALLED = 0; public static final int STATUS_INSTALLING = 1; public static final int STATUS_FAILED = 2; @@ -44,15 +54,19 @@ public abstract class PackageInstallerCompat { } } + public static UserHandle getUserHandle(SessionInfo info) { + return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle(); + } + /** * @return a map of active installs to their progress */ - public abstract HashMap updateAndGetActiveSessionCache(); + public abstract HashMap updateAndGetActiveSessionCache(); /** * @return an active SessionInfo for {@param pkg} or null if none exists. */ - public abstract PackageInstaller.SessionInfo getActiveSessionInfo(UserHandle user, String pkg); + public abstract SessionInfo getActiveSessionInfo(UserHandle user, String pkg); public abstract void onStop(); @@ -61,30 +75,44 @@ public abstract class PackageInstallerCompat { public final String packageName; public final int state; public final int progress; + public final UserHandle user; - private PackageInstallInfo(@NonNull PackageInstaller.SessionInfo info) { + private PackageInstallInfo(@NonNull SessionInfo info) { this.state = STATUS_INSTALLING; this.packageName = info.getAppPackageName(); this.componentName = new ComponentName(packageName, ""); this.progress = (int) (info.getProgress() * 100f); + this.user = getUserHandle(info); } - public PackageInstallInfo(String packageName, int state, int progress) { + public PackageInstallInfo(String packageName, int state, int progress, UserHandle user) { this.state = state; this.packageName = packageName; this.componentName = new ComponentName(packageName, ""); this.progress = progress; + this.user = user; } - public static PackageInstallInfo fromInstallingState(PackageInstaller.SessionInfo info) { + public static PackageInstallInfo fromInstallingState(SessionInfo info) { return new PackageInstallInfo(info); } - public static PackageInstallInfo fromState(int state, String packageName) { - return new PackageInstallInfo(packageName, state, 0 /* progress */); + public static PackageInstallInfo fromState(int state, String packageName, UserHandle user) { + return new PackageInstallInfo(packageName, state, 0 /* progress */, user); } } - public abstract List getAllVerifiedSessions(); + public abstract List getAllVerifiedSessions(); + + /** + * Returns true if a promise icon was already added to the home screen for {@param sessionId}. + * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS. + */ + public abstract boolean promiseIconAddedForId(int sessionId); + + /** + * Applicable only for icons with flag FLAG_PROMISE_NEW_INSTALLS. + */ + public abstract void removePromiseIconId(int sessionId); } diff --git a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java index 8a5eabca99..879d963c7c 100644 --- a/src/com/android/launcher3/compat/PackageInstallerCompatVL.java +++ b/src/com/android/launcher3/compat/PackageInstallerCompatVL.java @@ -21,17 +21,21 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller.SessionCallback; import android.content.pm.PackageInstaller.SessionInfo; +import android.content.pm.PackageManager; import android.os.Handler; -import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; import android.util.SparseArray; +import com.android.launcher3.SessionCommitReceiver; import com.android.launcher3.Utilities; import com.android.launcher3.icons.IconCache; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSet; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Thunk; import java.util.ArrayList; @@ -39,11 +43,13 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import static com.android.launcher3.Utilities.getPrefs; + public class PackageInstallerCompatVL extends PackageInstallerCompat { private static final boolean DEBUG = false; - @Thunk final SparseArray mActiveSessions = new SparseArray<>(); + @Thunk final SparseArray mActiveSessions = new SparseArray<>(); @Thunk final PackageInstaller mInstaller; private final IconCache mCache; @@ -51,6 +57,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { private final Context mAppContext; private final HashMap mSessionVerifiedMap = new HashMap<>(); private final LauncherAppsCompat mLauncherApps; + private final IntSet mPromiseIconIds; PackageInstallerCompatVL(Context context) { mAppContext = context.getApplicationContext(); @@ -59,17 +66,39 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { mWorker = new Handler(LauncherModel.getWorkerLooper()); mInstaller.registerSessionCallback(mCallback, mWorker); mLauncherApps = LauncherAppsCompat.getInstance(context); + mPromiseIconIds = IntSet.wrap(IntArray.wrap(Utilities.getIntArrayFromString( + getPrefs(context).getString(PROMISE_ICON_IDS, "")))); + + cleanUpPromiseIconIds(); + } + + private void cleanUpPromiseIconIds() { + IntArray existingIds = new IntArray(); + for (SessionInfo info : updateAndGetActiveSessionCache().values()) { + existingIds.add(info.getSessionId()); + } + IntArray idsToRemove = new IntArray(); + + for (int i = mPromiseIconIds.size() - 1; i >= 0; --i) { + if (!existingIds.contains(mPromiseIconIds.getArray().get(i))) { + idsToRemove.add(mPromiseIconIds.getArray().get(i)); + } + } + for (int i = idsToRemove.size() - 1; i >= 0; --i) { + mPromiseIconIds.getArray().removeValue(idsToRemove.get(i)); + } } @Override - public HashMap updateAndGetActiveSessionCache() { - HashMap activePackages = new HashMap<>(); - UserHandle primaryUser = Process.myUserHandle(); + public HashMap updateAndGetActiveSessionCache() { + HashMap activePackages = new HashMap<>(); for (SessionInfo info : getAllVerifiedSessions()) { - addSessionInfoToCache(info, Utilities.ATLEAST_Q ? info.getUser() : primaryUser); + addSessionInfoToCache(info, getUserHandle(info)); if (info.getAppPackageName() != null) { - activePackages.put(info.getAppPackageName(), info); - mActiveSessions.put(info.getSessionId(), info.getAppPackageName()); + activePackages.put(new PackageUserKey(info.getAppPackageName(), + getUserHandle(info)), info); + mActiveSessions.put(info.getSessionId(), + new PackageUserKey(info.getAppPackageName(), getUserHandle(info))); } } return activePackages; @@ -78,7 +107,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { public SessionInfo getActiveSessionInfo(UserHandle user, String pkg) { for (SessionInfo info : getAllVerifiedSessions()) { boolean match = pkg.equals(info.getAppPackageName()); - if (Utilities.ATLEAST_Q && !user.equals(info.getUser())) { + if (Utilities.ATLEAST_Q && !user.equals(getUserHandle(info))) { match = false; } if (match) { @@ -108,6 +137,30 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { } } + /** + * Add a promise app icon to the workspace iff: + * - The settings for it are enabled + * - The user installed the app + * - There is an app icon and label (For apps with no launching activity, no icon is provided). + * - The app is not already installed + * - A promise icon for the session has not already been created + */ + private void tryQueuePromiseAppIcon(SessionInfo sessionInfo) { + if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get() + && SessionCommitReceiver.isEnabled(mAppContext) + && verify(sessionInfo) != null + && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER + && sessionInfo.getAppIcon() != null + && !TextUtils.isEmpty(sessionInfo.getAppLabel()) + && !mPromiseIconIds.contains(sessionInfo.getSessionId()) + && mLauncherApps.getApplicationInfo(sessionInfo.getAppPackageName(), 0, + getUserHandle(sessionInfo)) == null) { + SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo); + mPromiseIconIds.add(sessionInfo.getSessionId()); + updatePromiseIconPrefs(); + } + } + private final SessionCallback mCallback = new SessionCallback() { @Override @@ -120,19 +173,31 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { PackageInstallInfo.fromInstallingState(sessionInfo)); } } + + tryQueuePromiseAppIcon(sessionInfo); } @Override public void onFinished(int sessionId, boolean success) { // For a finished session, we can't get the session info. So use the // packageName from our local cache. - String packageName = mActiveSessions.get(sessionId); + PackageUserKey key = mActiveSessions.get(sessionId); mActiveSessions.remove(sessionId); - if (packageName != null) { - sendUpdate(PackageInstallInfo.fromState( - success ? STATUS_INSTALLED : STATUS_FAILED, - packageName)); + if (key != null && key.mPackageName != null) { + String packageName = key.mPackageName; + sendUpdate(PackageInstallInfo.fromState(success ? STATUS_INSTALLED : STATUS_FAILED, + packageName, key.mUser)); + + if (!success && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get() + && mPromiseIconIds.contains(sessionId)) { + LauncherAppState appState = LauncherAppState.getInstanceNoCreate(); + if (appState != null) { + appState.getModel().onSessionFailure(packageName, key.mUser); + } + // If it is successful, the id is removed in the the package added flow. + removePromiseIconId(sessionId); + } } } @@ -149,14 +214,18 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { @Override public void onBadgingChanged(int sessionId) { - pushSessionDisplayToLauncher(sessionId); + SessionInfo sessionInfo = pushSessionDisplayToLauncher(sessionId); + if (sessionInfo != null) { + tryQueuePromiseAppIcon(sessionInfo); + } } private SessionInfo pushSessionDisplayToLauncher(int sessionId) { SessionInfo session = verify(mInstaller.getSessionInfo(sessionId)); if (session != null && session.getAppPackageName() != null) { - mActiveSessions.put(sessionId, session.getAppPackageName()); - addSessionInfoToCache(session, Process.myUserHandle()); + mActiveSessions.put(session.getSessionId(), + new PackageUserKey(session.getAppPackageName(), getUserHandle(session))); + addSessionInfoToCache(session, getUserHandle(session)); LauncherAppState app = LauncherAppState.getInstanceNoCreate(); if (app != null) { app.getModel().updateSessionDisplayInfo(session.getAppPackageName()); @@ -178,7 +247,7 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { if (!mSessionVerifiedMap.containsKey(pkg)) { LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(mAppContext); boolean hasSystemFlag = launcherApps.getApplicationInfo(pkg, - ApplicationInfo.FLAG_SYSTEM, Process.myUserHandle()) != null; + ApplicationInfo.FLAG_SYSTEM, getUserHandle(sessionInfo)) != null; mSessionVerifiedMap.put(pkg, DEBUG || hasSystemFlag); } } @@ -198,4 +267,23 @@ public class PackageInstallerCompatVL extends PackageInstallerCompat { } return list; } + + @Override + public boolean promiseIconAddedForId(int sessionId) { + return mPromiseIconIds.contains(sessionId); + } + + @Override + public void removePromiseIconId(int sessionId) { + if (mPromiseIconIds.contains(sessionId)) { + mPromiseIconIds.getArray().removeValue(sessionId); + updatePromiseIconPrefs(); + } + } + + private void updatePromiseIconPrefs() { + getPrefs(mAppContext).edit() + .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString()) + .apply(); + } } diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java index 45639e0a43..025087b9d8 100644 --- a/src/com/android/launcher3/config/BaseFlags.java +++ b/src/com/android/launcher3/config/BaseFlags.java @@ -18,20 +18,16 @@ package com.android.launcher3.config; import static androidx.core.util.Preconditions.checkNotNull; -import android.content.ContentResolver; import android.content.Context; import android.content.SharedPreferences; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.Looper; -import android.provider.Settings; import androidx.annotation.GuardedBy; import androidx.annotation.Keep; -import androidx.annotation.VisibleForTesting; +import androidx.annotation.VisibleForTesting; import com.android.launcher3.Utilities; +import com.android.launcher3.uioverrides.TogglableFlag; import java.util.ArrayList; import java.util.List; import java.util.SortedMap; @@ -41,11 +37,9 @@ import java.util.TreeMap; * Defines a set of flags used to control various launcher behaviors. * *

All the flags should be defined here with appropriate default values. - * - *

This class is kept package-private to prevent direct access. */ @Keep -abstract class BaseFlags { +public abstract class BaseFlags { private static final Object sLock = new Object(); @GuardedBy("sLock") @@ -66,6 +60,11 @@ abstract class BaseFlags { // When enabled the promise icon is visible in all apps while installation an app. public static final boolean LAUNCHER3_PROMISE_APPS_IN_ALL_APPS = false; + // When enabled a promise icon is added to the home screen when install session is active. + public static final TogglableFlag PROMISE_APPS_NEW_INSTALLS = + new TogglableFlag("PROMISE_APPS_NEW_INSTALLS", true, + "Adds a promise icon to the home screen for new install sessions."); + // Enable moving the QSB on the 0th screen of the workspace public static final boolean QSB_ON_FIRST_SCREEN = true; @@ -105,18 +104,22 @@ abstract class BaseFlags { "ENABLE_QUICKSTEP_LIVE_TILE", false, "Enable live tile in Quickstep overview"); public static final TogglableFlag ENABLE_HINTS_IN_OVERVIEW = new TogglableFlag( - "ENABLE_HINTS_IN_OVERVIEW", false, + "ENABLE_HINTS_IN_OVERVIEW", true, "Show chip hints and gleams on the overview screen"); public static final TogglableFlag FAKE_LANDSCAPE_UI = new TogglableFlag( "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout"); + public static final TogglableFlag APP_SEARCH_IMPROVEMENTS = new TogglableFlag( + "APP_SEARCH_IMPROVEMENTS", false, + "Adds localized title and keyword search and ranking"); + public static void initialize(Context context) { // Avoid the disk read for user builds if (Utilities.IS_DEBUG_DEVICE) { synchronized (sLock) { - for (TogglableFlag flag : sFlags) { + for (BaseTogglableFlag flag : sFlags) { flag.initialize(context); } } @@ -132,27 +135,30 @@ abstract class BaseFlags { SortedMap flagsByKey = new TreeMap<>(); synchronized (sLock) { for (TogglableFlag flag : sFlags) { - flagsByKey.put(flag.key, flag); + flagsByKey.put(((BaseTogglableFlag) flag).getKey(), flag); } } return new ArrayList<>(flagsByKey.values()); } - public static class TogglableFlag { + public static abstract class BaseTogglableFlag { private final String key; + // should be value that is hardcoded in client side. + // Comparatively, getDefaultValue() can be overridden. private final boolean defaultValue; private final String description; private boolean currentValue; - TogglableFlag( + public BaseTogglableFlag( String key, boolean defaultValue, String description) { this.key = checkNotNull(key); this.currentValue = this.defaultValue = defaultValue; this.description = checkNotNull(description); + synchronized (sLock) { - sFlags.add(this); + sFlags.add((TogglableFlag)this); } } @@ -162,18 +168,22 @@ abstract class BaseFlags { currentValue = value; } - @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public String getKey() { return key; } - void initialize(Context context) { - currentValue = getFromStorage(context, defaultValue); + + protected void initialize(Context context) { + currentValue = getFromStorage(context, getDefaultValue()); } + protected abstract boolean getOverridenDefaultValue(boolean value); + + protected abstract void addChangeListener(Context context, Runnable r); + public void updateStorage(Context context, boolean value) { SharedPreferences.Editor editor = context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE).edit(); - if (value == defaultValue) { + if (value == getDefaultValue()) { editor.remove(key).apply(); } else { editor.putBoolean(key, value).apply(); @@ -182,11 +192,11 @@ abstract class BaseFlags { boolean getFromStorage(Context context, boolean defaultValue) { return context.getSharedPreferences(FLAGS_PREF_NAME, Context.MODE_PRIVATE) - .getBoolean(key, defaultValue); + .getBoolean(key, getDefaultValue()); } boolean getDefaultValue() { - return defaultValue; + return getOverridenDefaultValue(defaultValue); } /** Returns the value of the flag at process start, including any overrides present. */ @@ -203,6 +213,8 @@ abstract class BaseFlags { return "TogglableFlag{" + "key=" + key + ", " + "defaultValue=" + defaultValue + ", " + + "overriddenDefaultValue=" + getOverridenDefaultValue(defaultValue) + ", " + + "currentValue=" + currentValue + ", " + "description=" + description + "}"; } @@ -213,9 +225,9 @@ abstract class BaseFlags { return true; } if (o instanceof TogglableFlag) { - TogglableFlag that = (TogglableFlag) o; + BaseTogglableFlag that = (BaseTogglableFlag) o; return (this.key.equals(that.getKey())) - && (this.defaultValue == that.getDefaultValue()) + && (this.getDefaultValue() == that.getDefaultValue()) && (this.description.equals(that.getDescription())); } return false; @@ -227,54 +239,10 @@ abstract class BaseFlags { h$ *= 1000003; h$ ^= key.hashCode(); h$ *= 1000003; - h$ ^= defaultValue ? 1231 : 1237; + h$ ^= getDefaultValue() ? 1231 : 1237; h$ *= 1000003; h$ ^= description.hashCode(); return h$; } } - - /** - * Stores the FeatureFlag's value in Settings.Global instead of our SharedPrefs. - * This is useful if we want to be able to control this flag from another process. - */ - public static final class ToggleableGlobalSettingsFlag extends TogglableFlag { - private ContentResolver contentResolver; - - ToggleableGlobalSettingsFlag(String key, boolean defaultValue, String description) { - super(key, defaultValue, description); - } - - @Override - public void initialize(Context context) { - contentResolver = context.getContentResolver(); - contentResolver.registerContentObserver(Settings.Global.getUriFor(getKey()), true, - new ContentObserver(new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange) { - superInitialize(context); - }}); - superInitialize(context); - } - - private void superInitialize(Context context) { - super.initialize(context); - } - - @Override - public void updateStorage(Context context, boolean value) { - if (contentResolver == null) { - return; - } - Settings.Global.putInt(contentResolver, getKey(), value ? 1 : 0); - } - - @Override - boolean getFromStorage(Context context, boolean defaultValue) { - if (contentResolver == null) { - return defaultValue; - } - return Settings.Global.getInt(contentResolver, getKey(), defaultValue ? 1 : 0) == 1; - } - } } diff --git a/src/com/android/launcher3/config/FlagTogglerPrefUi.java b/src/com/android/launcher3/config/FlagTogglerPrefUi.java index 5ecb186500..54e5322bd5 100644 --- a/src/com/android/launcher3/config/FlagTogglerPrefUi.java +++ b/src/com/android/launcher3/config/FlagTogglerPrefUi.java @@ -26,12 +26,13 @@ import android.view.MenuItem; import android.widget.Toast; import com.android.launcher3.R; -import com.android.launcher3.config.BaseFlags.TogglableFlag; import androidx.preference.PreferenceDataStore; import androidx.preference.PreferenceFragment; import androidx.preference.PreferenceGroup; import androidx.preference.SwitchPreference; +import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; +import com.android.launcher3.uioverrides.TogglableFlag; /** * Dev-build only UI allowing developers to toggle flag settings. See {@link FeatureFlags}. @@ -62,7 +63,7 @@ public final class FlagTogglerPrefUi { @Override public boolean getBoolean(String key, boolean defaultValue) { - for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { + for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) { if (flag.getKey().equals(key)) { return flag.getFromStorage(mContext, defaultValue); } @@ -83,7 +84,7 @@ public final class FlagTogglerPrefUi { // flag with a different value than the default. That way, when we flip flags in // future, engineers will pick up the new value immediately. To accomplish this, we use a // custom preference data store. - for (TogglableFlag flag : FeatureFlags.getTogglableFlags()) { + for (BaseTogglableFlag flag : FeatureFlags.getTogglableFlags()) { SwitchPreference switchPreference = new SwitchPreference(mContext); switchPreference.setKey(flag.getKey()); switchPreference.setDefaultValue(flag.getDefaultValue()); @@ -99,7 +100,7 @@ public final class FlagTogglerPrefUi { /** * Updates the summary to show the description and whether the flag overrides the default value. */ - private void updateSummary(SwitchPreference switchPreference, TogglableFlag flag) { + private void updateSummary(SwitchPreference switchPreference, BaseTogglableFlag flag) { String onWarning = flag.getDefaultValue() ? "" : "OVERRIDDEN
"; String offWarning = flag.getDefaultValue() ? "OVERRIDDEN
" : ""; switchPreference.setSummaryOn(Html.fromHtml(onWarning + flag.getDescription())); @@ -134,7 +135,7 @@ public final class FlagTogglerPrefUi { } } - private boolean getFlagStateFromSharedPrefs(TogglableFlag flag) { + private boolean getFlagStateFromSharedPrefs(BaseTogglableFlag flag) { return mDataStore.getBoolean(flag.getKey(), flag.getDefaultValue()); } diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java index d32dd2eb98..b72fd988a3 100644 --- a/src/com/android/launcher3/dragndrop/DragController.java +++ b/src/com/android/launcher3/dragndrop/DragController.java @@ -579,6 +579,9 @@ public class DragController implements DragDriver.EventListener, TouchController } private void drop(DropTarget dropTarget, Runnable flingAnimation) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragController.drop"); + } final int[] coordinates = mCoordinatesTemp; mDragObject.x = coordinates[0]; mDragObject.y = coordinates[1]; diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java index 84fc94dd25..01e0f923c1 100644 --- a/src/com/android/launcher3/dragndrop/DragDriver.java +++ b/src/com/android/launcher3/dragndrop/DragDriver.java @@ -17,10 +17,12 @@ package com.android.launcher3.dragndrop; import android.content.Context; +import android.util.Log; import android.view.DragEvent; import android.view.MotionEvent; import com.android.launcher3.DropTarget.DragObject; +import com.android.launcher3.testing.TestProtocol; /** * Base class for driving a drag/drop operation. @@ -52,10 +54,16 @@ public abstract class DragDriver { mEventListener.onDriverDragMove(ev.getX(), ev.getY()); break; case MotionEvent.ACTION_UP: + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_UP"); + } mEventListener.onDriverDragMove(ev.getX(), ev.getY()); mEventListener.onDriverDragEnd(ev.getX(), ev.getY()); break; case MotionEvent.ACTION_CANCEL: + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, "DragDriver.ACTION_CANCEL"); + } mEventListener.onDriverDragCancel(); break; } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 2ef6d707e3..f22b533380 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -516,7 +516,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { - mFolderIcon.setBackgroundVisible(false); + mFolderIcon.setIconVisible(false); mFolderIcon.drawLeaveBehindIfExists(); } @Override @@ -646,7 +646,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo clearFocus(); if (mFolderIcon != null) { mFolderIcon.setVisibility(View.VISIBLE); - mFolderIcon.setBackgroundVisible(true); + mFolderIcon.setIconVisible(true); mFolderIcon.mFolderName.setTextVisibility(true); if (wasAnimated) { mFolderIcon.animateBgShadowAndStroke(); diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 250169cdb2..0e2d4673e4 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -65,6 +65,7 @@ import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.icons.DotRenderer; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; +import com.android.launcher3.views.IconLabelDotView; import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; @@ -73,7 +74,7 @@ import java.util.List; /** * An icon that can appear on in the workspace representing an {@link Folder}. */ -public class FolderIcon extends FrameLayout implements FolderListener { +public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView { @Thunk Launcher mLauncher; @Thunk Folder mFolder; @@ -107,6 +108,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { private Alarm mOpenAlarm = new Alarm(); + private boolean mForceHideDot; @ViewDebug.ExportedProperty(category = "launcher", deepExport = true) private FolderDotInfo mDotInfo = new FolderDotInfo(); private DotRenderer mDotRenderer; @@ -409,6 +411,20 @@ public class FolderIcon extends FrameLayout implements FolderListener { return mPreviewLayoutRule; } + @Override + public void setForceHideDot(boolean forceHideDot) { + if (mForceHideDot == forceHideDot) { + return; + } + mForceHideDot = forceHideDot; + + if (forceHideDot) { + invalidate(); + } else if (hasDot()) { + animateDotScale(0, 1); + } + } + /** * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false * (the dot is being added or removed). @@ -468,7 +484,8 @@ public class FolderIcon extends FrameLayout implements FolderListener { mBackground.setInvalidateDelegate(this); } - public void setBackgroundVisible(boolean visible) { + @Override + public void setIconVisible(boolean visible) { mBackgroundIsVisible = visible; invalidate(); } @@ -509,7 +526,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { } public void drawDot(Canvas canvas) { - if ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0) { + if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) { Rect iconBounds = mDotParams.iconBounds; BubbleTextView.getIconBounds(this, iconBounds, mLauncher.getWallpaperDeviceProfile().iconSizePx); diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index c9566cb145..288749fa7e 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -17,6 +17,7 @@ package com.android.launcher3.graphics; import static com.android.launcher3.graphics.IconShape.getShapePath; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.content.Context; import android.content.pm.ActivityInfo; @@ -31,6 +32,8 @@ import android.os.Process; import android.os.UserHandle; import android.util.ArrayMap; +import androidx.annotation.UiThread; + import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; @@ -38,16 +41,13 @@ import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; -import androidx.annotation.UiThread; - /** * Factory for creating new drawables. */ public class DrawableFactory implements ResourceBasedOverride { public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(c -> Overrides.getObject(DrawableFactory.class, - c.getApplicationContext(), R.string.drawable_factory_class)); + forOverride(DrawableFactory.class, R.string.drawable_factory_class); protected final UserHandle mMyUser = Process.myUserHandle(); protected final ArrayMap mUserBadges = new ArrayMap<>(); diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 648445e40c..abff237e8b 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -29,6 +29,8 @@ import android.os.Process; import android.os.UserHandle; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.launcher3.AppInfo; import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; @@ -36,10 +38,11 @@ import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; import com.android.launcher3.MainThreadExecutor; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.cache.BaseIconCache; import com.android.launcher3.icons.cache.CachingLogic; @@ -50,8 +53,6 @@ import com.android.launcher3.util.Preconditions; import java.util.function.Supplier; -import androidx.annotation.NonNull; - /** * Cache of application icons. Icons can be made from any thread. */ @@ -75,11 +76,11 @@ public class IconCache extends BaseIconCache { super(context, LauncherFiles.APP_ICONS_DB, LauncherModel.getWorkerLooper(), inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */); mComponentWithLabelCachingLogic = new ComponentCachingLogic(context); - mLauncherActivityInfoCachingLogic = new LauncherActivtiyCachingLogic(this); + mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context); mLauncherApps = LauncherAppsCompat.getInstance(mContext); mUserManager = UserManagerCompat.getInstance(mContext); mInstantAppResolver = InstantAppResolver.newInstance(mContext); - mIconProvider = IconProvider.newInstance(context); + mIconProvider = IconProvider.INSTANCE.get(context); } @Override @@ -237,7 +238,8 @@ public class IconCache extends BaseIconCache { @Override protected String getIconSystemState(String packageName) { - return mIconProvider.getSystemStateForPackage(mSystemState, packageName); + return mIconProvider.getSystemStateForPackage(mSystemState, packageName) + + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get(); } public static abstract class IconLoadRequest extends HandlerRunnable { diff --git a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java similarity index 67% rename from src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java rename to src/com/android/launcher3/icons/LauncherActivityCachingLogic.java index 7c996339bd..f9a94daf53 100644 --- a/src/com/android/launcher3/icons/LauncherActivtiyCachingLogic.java +++ b/src/com/android/launcher3/icons/LauncherActivityCachingLogic.java @@ -20,14 +20,23 @@ import android.content.Context; import android.content.pm.LauncherActivityInfo; import android.os.UserHandle; +import com.android.launcher3.IconProvider; +import com.android.launcher3.R; import com.android.launcher3.icons.cache.CachingLogic; +import com.android.launcher3.util.ResourceBasedOverride; -public class LauncherActivtiyCachingLogic implements CachingLogic { +/** + * Caching logic for LauncherActivityInfo. + */ +public class LauncherActivityCachingLogic + implements CachingLogic, ResourceBasedOverride { - private final IconCache mCache; - - public LauncherActivtiyCachingLogic(IconCache cache) { - mCache = cache; + /** + * Creates and returns a new instance + */ + public static LauncherActivityCachingLogic newInstance(Context context) { + return Overrides.getObject(LauncherActivityCachingLogic.class, context, + R.string.launcher_activity_logic_class); } @Override @@ -49,8 +58,10 @@ public class LauncherActivtiyCachingLogic implements CachingLogic=0 ? newContainerTarget(dstContainerType) : null); } + public void logActionCommand(int command, int srcContainerType, int dstContainerType, + int pageIndex) { + Target srcTarget = newContainerTarget(srcContainerType); + srcTarget.pageIndex = pageIndex; + logActionCommand(command, srcTarget, + dstContainerType >=0 ? newContainerTarget(dstContainerType) : null); + } + public void logActionCommand(int command, Target srcTarget, Target dstTarget) { LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget); if (command == Action.Command.STOP) { diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java index ed0d470801..7d4f2f7225 100644 --- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -16,6 +16,8 @@ package com.android.launcher3.model; import android.content.Intent; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInstaller.SessionInfo; import android.os.UserHandle; import android.util.LongSparseArray; import android.util.Pair; @@ -32,6 +34,8 @@ import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherSettings; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; @@ -85,6 +89,10 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { } } + PackageInstallerCompat packageInstaller = + PackageInstallerCompat.getInstance(app.getContext()); + LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(app.getContext()); + for (ItemInfo item : filteredItems) { // Find appropriate space for the item. int[] coords = findSpaceForItem(app, dataModel, workspaceScreens, @@ -101,6 +109,36 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask { throw new RuntimeException("Unexpected info type"); } + if (item instanceof WorkspaceItemInfo && ((WorkspaceItemInfo) item).isPromise()) { + WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) item; + String packageName = item.getTargetComponent() != null + ? item.getTargetComponent().getPackageName() : null; + if (packageName == null) { + continue; + } + SessionInfo sessionInfo = packageInstaller.getActiveSessionInfo(item.user, + packageName); + if (sessionInfo == null) { + List activities = launcherApps + .getActivityList(packageName, item.user); + if (activities != null && !activities.isEmpty()) { + // App was installed while launcher was in the background. + itemInfo = new AppInfo(app.getContext(), activities.get(0), item.user) + .makeWorkspaceItem(); + WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo; + wii.title = ""; + wii.applyFrom(app.getIconCache().getDefaultIcon(item.user)); + app.getIconCache().getTitleAndIcon(wii, + ((WorkspaceItemInfo) itemInfo).usingLowResIcon()); + } else { + // Session was cancelled, do not add. + continue; + } + } else { + workspaceInfo.setInstallProgress((int) sessionInfo.getProgress()); + } + } + // Add the shortcut to the db getModelWriter().addItemToDatabase(itemInfo, LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java index 1613d47b9f..29a46cfa5c 100644 --- a/src/com/android/launcher3/model/AppLaunchTracker.java +++ b/src/com/android/launcher3/model/AppLaunchTracker.java @@ -15,18 +15,18 @@ */ package com.android.launcher3.model; -import static com.android.launcher3.util.ResourceBasedOverride.Overrides.getObject; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; import android.content.ComponentName; import android.os.UserHandle; +import androidx.annotation.Nullable; + import com.android.launcher3.R; import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; -import androidx.annotation.Nullable; - /** * Callback for receiving various app launch events */ @@ -43,8 +43,7 @@ public class AppLaunchTracker implements ResourceBasedOverride { public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(c -> - getObject(AppLaunchTracker.class, c, R.string.app_launch_tracker_class)); + forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class); public void onStartShortcut(String packageName, String shortcutId, UserHandle user, @Nullable String container) { } diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java index 1149b553f6..a0b7177630 100644 --- a/src/com/android/launcher3/model/FirstScreenBroadcast.java +++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java @@ -26,6 +26,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.HashMap; @@ -34,6 +35,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import static android.os.Process.myUserHandle; + /** * Helper class to send broadcasts to package installers that have: * - Items on the first screen @@ -60,7 +63,7 @@ public class FirstScreenBroadcast { private final MultiHashMap mPackagesForInstaller; - public FirstScreenBroadcast(HashMap sessionInfoForPackage) { + public FirstScreenBroadcast(HashMap sessionInfoForPackage) { mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage); } @@ -69,11 +72,13 @@ public class FirstScreenBroadcast { * of packages with active sessions for that installer. */ private MultiHashMap getPackagesForInstaller( - HashMap sessionInfoForPackage) { + HashMap sessionInfoForPackage) { MultiHashMap packagesForInstaller = new MultiHashMap<>(); - for (Map.Entry entry : sessionInfoForPackage.entrySet()) { - packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(), - entry.getKey()); + for (Map.Entry entry : sessionInfoForPackage.entrySet()) { + if (myUserHandle().equals(entry.getKey().mUser)) { + packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(), + entry.getKey().mPackageName); + } } return packagesForInstaller; } diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index faecc067e4..783e908e30 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -34,10 +34,12 @@ import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.PackageUserKey; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.function.Consumer; import androidx.annotation.VisibleForTesting; @@ -970,8 +972,9 @@ public class GridSizeMigrationTask { .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) { validPackages.add(info.packageName); } - validPackages.addAll(PackageInstallerCompat.getInstance(context) - .updateAndGetActiveSessionCache().keySet()); + PackageInstallerCompat.getInstance(context) + .updateAndGetActiveSessionCache().keySet() + .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName)); return validPackages; } diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 1a03b77714..1c39d1f23c 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -227,7 +227,7 @@ public class LoaderCursor extends CursorWrapper { if (!TextUtils.isEmpty(title)) { info.title = Utilities.trim(title); } - } else if (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) { + } else if (hasRestoreFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON)) { if (TextUtils.isEmpty(info.title)) { info.title = getTitle(); } diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 0138572d0c..7593a3371a 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -19,6 +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.compat.PackageInstallerCompat.getUserHandle; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; import android.appwidget.AppWidgetProviderInfo; @@ -49,8 +50,8 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherSettings; -import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; @@ -61,7 +62,7 @@ import com.android.launcher3.folder.FolderIconPreviewVerifier; import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic; import com.android.launcher3.icons.IconCache; -import com.android.launcher3.icons.LauncherActivtiyCachingLogic; +import com.android.launcher3.icons.LauncherActivityCachingLogic; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.cache.IconCacheUpdateHandler; import com.android.launcher3.logging.FileLog; @@ -72,6 +73,7 @@ import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.LooperIdleLock; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.TraceHelper; import java.util.ArrayList; @@ -196,7 +198,7 @@ public class LoaderTask implements Runnable { IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); setIgnorePackages(updateHandler); updateHandler.updateIcons(allActivityList, - new LauncherActivtiyCachingLogic(mApp.getIconCache()), + LauncherActivityCachingLogic.newInstance(mApp.getContext()), mApp.getModel()::onPackageIconsUpdated); // Take a break @@ -281,8 +283,9 @@ public class LoaderTask implements Runnable { synchronized (mBgDataModel) { mBgDataModel.clear(); - final HashMap installingPkgs = + final HashMap installingPkgs = mPackageInstaller.updateAndGetActiveSessionCache(); + final PackageUserKey tempPackageKey = new PackageUserKey(null, null); mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs); Map shortcutKeyToPinnedShortcuts = new HashMap<>(); @@ -419,9 +422,10 @@ public class LoaderTask implements Runnable { // installed later. FileLog.d(TAG, "package not yet restored: " + targetPkg); + tempPackageKey.update(targetPkg, c.user); if (c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED)) { // Restore has started once. - } else if (installingPkgs.containsKey(targetPkg)) { + } else if (installingPkgs.containsKey(tempPackageKey)) { // App restore has started. Update the flag c.restoreFlag |= WorkspaceItemInfo.FLAG_RESTORE_STARTED; c.updater().put(LauncherSettings.Favorites.RESTORED, @@ -536,7 +540,8 @@ public class LoaderTask implements Runnable { } if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) { - SessionInfo si = installingPkgs.get(targetPkg); + tempPackageKey.update(targetPkg, c.user); + SessionInfo si = installingPkgs.get(tempPackageKey); if (si == null) { info.status &= ~WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE; } else { @@ -630,8 +635,10 @@ public class LoaderTask implements Runnable { appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, component); appWidgetInfo.restoreStatus = c.restoreFlag; + + tempPackageKey.update(component.getPackageName(), c.user); SessionInfo si = - installingPkgs.get(component.getPackageName()); + installingPkgs.get(tempPackageKey); Integer installProgress = si == null ? null : (int) (si.getProgress() * 100); diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java index 5f6d1281bd..9fcab38870 100644 --- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -18,7 +18,6 @@ package com.android.launcher3.model; import android.content.ComponentName; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.Process; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; @@ -56,7 +55,7 @@ public class PackageInstallStateChangedTask extends BaseModelUpdateTask { ApplicationInfo ai = app.getContext() .getPackageManager().getApplicationInfo(mInstallInfo.packageName, 0); if (InstantAppResolver.newInstance(app.getContext()).isInstantApp(ai)) { - app.getModel().onPackageAdded(ai.packageName, Process.myUserHandle()); + app.getModel().onPackageAdded(ai.packageName, mInstallInfo.user); } } catch (PackageManager.NameNotFoundException e) { // Ignore diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index c37ed99522..4428c8e6df 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -43,6 +43,7 @@ import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.shortcuts.DeepShortcutManager; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; @@ -55,6 +56,8 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import static com.android.launcher3.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON; + /** * Handles updates due to changes in package manager (app installed/updated/removed) * or when a user availability changes. @@ -85,6 +88,10 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { @Override public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) { + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.APP_NOT_DISABLED, "PackageUpdatedTask: " + mOp + ", " + + Arrays.toString(mPackages)); + } final Context context = app.getContext(); final IconCache iconCache = app.getIconCache(); @@ -99,7 +106,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); iconCache.updateIconsForPkg(packages[i], mUser); if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { - appsList.removePackage(packages[i], Process.myUserHandle()); + appsList.removePackage(packages[i], mUser); } appsList.addPackage(context, packages[i], mUser); @@ -227,8 +234,7 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { isTargetValid = LauncherAppsCompat.getInstance(context) .isActivityEnabledForProfile(cn, mUser); } - if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON) - && !isTargetValid) { + if (si.hasStatusFlag(FLAG_AUTOINSTALL_ICON)) { if (updateWorkspaceItemIntent(context, si, packageName)) { infoUpdated = true; } else if (si.hasPromiseIconUi()) { diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java index 5050457b9b..a1917ecb00 100644 --- a/src/com/android/launcher3/notification/NotificationKeyData.java +++ b/src/com/android/launcher3/notification/NotificationKeyData.java @@ -17,13 +17,17 @@ package com.android.launcher3.notification; import android.app.Notification; +import android.app.Person; import android.service.notification.StatusBarNotification; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.launcher3.Utilities; + import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; - /** * The key data associated with the notification, used to determine what to include * in dots and dummy popup views before they are populated. @@ -33,20 +37,27 @@ import androidx.annotation.NonNull; public class NotificationKeyData { public final String notificationKey; public final String shortcutId; + @NonNull + public final String[] personKeysFromNotification; public int count; - private NotificationKeyData(String notificationKey, String shortcutId, int count) { + private NotificationKeyData(String notificationKey, String shortcutId, int count, + String[] personKeysFromNotification) { this.notificationKey = notificationKey; this.shortcutId = shortcutId; this.count = Math.max(1, count); + this.personKeysFromNotification = personKeysFromNotification; } public static NotificationKeyData fromNotification(StatusBarNotification sbn) { Notification notif = sbn.getNotification(); - return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number); + return new NotificationKeyData(sbn.getKey(), notif.getShortcutId(), notif.number, + extractPersonKeyOnly(notif.extras.getParcelableArrayList( + Notification.EXTRA_PEOPLE_LIST))); } - public static List extractKeysOnly(@NonNull List notificationKeys) { + public static List extractKeysOnly( + @NonNull List notificationKeys) { List keysOnly = new ArrayList<>(notificationKeys.size()); for (NotificationKeyData notificationKeyData : notificationKeys) { keysOnly.add(notificationKeyData.notificationKey); @@ -54,6 +65,14 @@ public class NotificationKeyData { return keysOnly; } + private static String[] extractPersonKeyOnly(@Nullable ArrayList people) { + if (people == null || people.isEmpty()) { + return Utilities.EMPTY_STRING_ARRAY; + } + return people.stream().filter(person -> person.getKey() != null) + .map(Person::getKey).sorted().toArray(String[]::new); + } + @Override public boolean equals(Object obj) { if (!(obj instanceof NotificationKeyData)) { diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 25d9f7976a..15fb4cea6f 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -36,7 +36,6 @@ import android.os.Build; import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; -import android.util.Log; import android.util.Pair; import android.view.MotionEvent; import android.view.View; @@ -53,7 +52,6 @@ import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherModel; import com.android.launcher3.R; -import com.android.launcher3.Utilities; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; import com.android.launcher3.dot.DotInfo; @@ -65,13 +63,12 @@ import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationItemView; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener; -import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; -import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.views.BaseDragLayer; import java.util.ArrayList; @@ -201,7 +198,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, return null; } ItemInfo itemInfo = (ItemInfo) icon.getTag(); - if (!DeepShortcutManager.supportsShortcuts(itemInfo)) { + if (!ShortcutUtil.supportsShortcuts(itemInfo)) { return null; } @@ -300,7 +297,7 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, } mLauncher.getDragController().addDragListener(this); - mOriginalIcon.forceHideDot(true); + mOriginalIcon.setForceHideDot(true); // All views are added. Animate layout from now on. setLayoutTransition(new LayoutTransition()); @@ -563,14 +560,14 @@ public class PopupContainerWithArrow extends ArrowPopup implements DragSource, protected void onCreateCloseAnimation(AnimatorSet anim) { // Animate original icon's text back in. anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); - mOriginalIcon.forceHideDot(false); + mOriginalIcon.setForceHideDot(false); } @Override protected void closeComplete() { super.closeComplete(); mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); - mOriginalIcon.forceHideDot(false); + mOriginalIcon.setForceHideDot(false); } @Override diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index 2d301ac008..4612b2a474 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -29,17 +29,22 @@ import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.ShortcutUtil; import com.android.launcher3.widget.WidgetListRowEntry; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; /** * Provides data for the popup menu that appears after long-clicking on apps. @@ -129,7 +134,8 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan for (PackageUserKey packageUserKey : mPackageUserToDotInfos.keySet()) { DotInfo prevDot = updatedDots.get(packageUserKey); DotInfo newDot = mPackageUserToDotInfos.get(packageUserKey); - if (prevDot == null) { + if (prevDot == null + || prevDot.getNotificationCount() != newDot.getNotificationCount()) { updatedDots.put(packageUserKey, newDot); } else { // No need to update the dot if it already existed (no visual change). @@ -155,7 +161,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan } public int getShortcutCountForItem(ItemInfo info) { - if (!DeepShortcutManager.supportsShortcuts(info)) { + if (!ShortcutUtil.supportsDeepShortcuts(info)) { return 0; } ComponentName component = info.getTargetComponent(); @@ -167,17 +173,26 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan return count == null ? 0 : count; } - public DotInfo getDotInfoForItem(ItemInfo info) { - if (!DeepShortcutManager.supportsShortcuts(info)) { + public @Nullable DotInfo getDotInfoForItem(@NonNull ItemInfo info) { + if (!ShortcutUtil.supportsShortcuts(info)) { return null; } - - return mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info)); + DotInfo dotInfo = mPackageUserToDotInfos.get(PackageUserKey.fromItemInfo(info)); + if (dotInfo == null) { + return null; + } + List notifications = getNotificationsForItem( + info, dotInfo.getNotificationKeys()); + if (notifications.isEmpty()) { + return null; + } + return dotInfo; } public @NonNull List getNotificationKeysForItem(ItemInfo info) { DotInfo dotInfo = getDotInfoForItem(info); - return dotInfo == null ? Collections.EMPTY_LIST : dotInfo.getNotificationKeys(); + return dotInfo == null ? Collections.EMPTY_LIST + : getNotificationsForItem(info, dotInfo.getNotificationKeys()); } /** This makes a potentially expensive binder call and should be run on a background thread. */ @@ -226,6 +241,27 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan return null; } + /** + * Returns a list of notifications that are relevant to given ItemInfo. + */ + public static @NonNull List getNotificationsForItem( + @NonNull ItemInfo info, @NonNull List notifications) { + String shortcutId = ShortcutUtil.getShortcutIdIfPinnedShortcut(info); + if (shortcutId == null) { + return notifications; + } + String[] personKeys = ShortcutUtil.getPersonKeysIfPinnedShortcut(info); + return notifications.stream().filter((NotificationKeyData notification) -> { + if (notification.shortcutId != null) { + return notification.shortcutId.equals(shortcutId); + } + if (notification.personKeysFromNotification.length != 0) { + return Arrays.equals(notification.personKeysFromNotification, personKeys); + } + return false; + }).collect(Collectors.toList()); + } + public interface PopupDataChangeListener { PopupDataChangeListener INSTANCE = new PopupDataChangeListener() { }; diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java index 41ab4df7bf..5a5fbabacf 100644 --- a/src/com/android/launcher3/popup/RemoteActionShortcut.java +++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java @@ -29,11 +29,12 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseDraggingActivity; import com.android.launcher3.ItemInfo; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.userevent.nano.LauncherLogProto; public class RemoteActionShortcut extends SystemShortcut { private static final String TAG = "RemoteActionShortcut"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Utilities.IS_DEBUG_DEVICE; private final RemoteAction mAction; diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java index 563f3b3c65..78bd81b464 100644 --- a/src/com/android/launcher3/popup/SystemShortcut.java +++ b/src/com/android/launcher3/popup/SystemShortcut.java @@ -131,6 +131,7 @@ public abstract class SystemShortcut extends Ite @Override public View.OnClickListener getOnClickListener(final Launcher launcher, final ItemInfo itemInfo) { + if (itemInfo.getTargetComponent() == null) return null; final List widgets = launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey( itemInfo.getTargetComponent().getPackageName(), itemInfo.user)); diff --git a/src/com/android/launcher3/popup/SystemShortcutFactory.java b/src/com/android/launcher3/popup/SystemShortcutFactory.java index 516fafad54..37a209289e 100644 --- a/src/com/android/launcher3/popup/SystemShortcutFactory.java +++ b/src/com/android/launcher3/popup/SystemShortcutFactory.java @@ -15,6 +15,10 @@ */ package com.android.launcher3.popup; +import static com.android.launcher3.util.MainThreadInitializedObject.forOverride; + +import androidx.annotation.NonNull; + import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.R; @@ -24,13 +28,10 @@ import com.android.launcher3.util.ResourceBasedOverride; import java.util.ArrayList; import java.util.List; -import androidx.annotation.NonNull; - public class SystemShortcutFactory implements ResourceBasedOverride { public static final MainThreadInitializedObject INSTANCE = - new MainThreadInitializedObject<>(c -> Overrides.getObject( - SystemShortcutFactory.class, c, R.string.system_shortcut_factory_class)); + forOverride(SystemShortcutFactory.class, R.string.system_shortcut_factory_class); /** Note that these are in order of priority. */ private final SystemShortcut[] mAllShortcuts; diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java index 3c0c5fddea..d643a0b493 100644 --- a/src/com/android/launcher3/provider/RestoreDbTask.java +++ b/src/com/android/launcher3/provider/RestoreDbTask.java @@ -173,12 +173,6 @@ public class RestoreDbTask { values.put(Favorites.PROFILE_ID, newProfileId); db.update(Favorites.TABLE_NAME, values, "profileId = ?", new String[]{Long.toString(oldProfileId)}); - - // Change default value of the column. - db.execSQL("ALTER TABLE favorites RENAME TO favorites_old;"); - Favorites.addTableToDb(db, newProfileId, false); - db.execSQL("INSERT INTO favorites SELECT * FROM favorites_old;"); - dropTable(db, "favorites_old"); } diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java index bab454f070..790a2e8448 100644 --- a/src/com/android/launcher3/testing/TestInformationHandler.java +++ b/src/com/android/launcher3/testing/TestInformationHandler.java @@ -15,8 +15,13 @@ */ package com.android.launcher3.testing; +import static android.graphics.Bitmap.Config.ARGB_8888; + import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Color; import android.os.Bundle; +import android.os.Debug; import com.android.launcher3.DeviceProfile; import com.android.launcher3.InvariantDeviceProfile; @@ -28,6 +33,7 @@ import com.android.launcher3.R; import com.android.launcher3.allapps.AllAppsStore; import com.android.launcher3.util.ResourceBasedOverride; +import java.util.LinkedList; import java.util.concurrent.ExecutionException; public class TestInformationHandler implements ResourceBasedOverride { @@ -41,6 +47,7 @@ public class TestInformationHandler implements ResourceBasedOverride { protected DeviceProfile mDeviceProfile; protected LauncherAppState mLauncherAppState; protected Launcher mLauncher; + private static LinkedList mLeaks; public void init(Context context) { mContext = context; @@ -112,7 +119,37 @@ public class TestInformationHandler implements ResourceBasedOverride { } break; } + + case TestProtocol.REQUEST_TOTAL_PSS_KB: { + Debug.MemoryInfo mem = new Debug.MemoryInfo(); + Debug.getMemoryInfo(mem); + response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, mem.getTotalPss()); + break; + } + + case TestProtocol.REQUEST_JAVA_LEAK: { + if (mLeaks == null) mLeaks = new LinkedList(); + + // Allocate and dirty the memory. + final int leakSize = 1024 * 1024; + final byte[] bytes = new byte[leakSize]; + for (int i = 0; i < leakSize; i += 239) { + bytes[i] = (byte) (i % 256); + } + mLeaks.add(bytes); + break; + } + + case TestProtocol.REQUEST_NATIVE_LEAK: { + if (mLeaks == null) mLeaks = new LinkedList(); + + // Allocate and dirty a bitmap. + final Bitmap bitmap = Bitmap.createBitmap(512, 512, ARGB_8888); + bitmap.eraseColor(Color.RED); + mLeaks.add(bitmap); + break; + } } return response; } -} +} \ No newline at end of file diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java index 9846a04271..232a764e01 100644 --- a/src/com/android/launcher3/testing/TestProtocol.java +++ b/src/com/android/launcher3/testing/TestProtocol.java @@ -73,10 +73,17 @@ public final class TestProtocol { public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags"; public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin"; public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin"; + public static final String REQUEST_TOTAL_PSS_KB = "total_pss"; + public static final String REQUEST_JAVA_LEAK = "java-leak"; + public static final String REQUEST_NATIVE_LEAK = "native-leak"; public static boolean sDebugTracing = false; 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 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"; + public static final String ALL_APPS_UPON_RECENTS = "b/139941530"; + public static final String STABLE_STATE_MISMATCH = "b/140311911"; } diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 0545344170..c5ba5bab6a 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -69,6 +69,7 @@ public abstract class AbstractStateChangeTouchController protected final SwipeDetector.Direction mSwipeDirection; private boolean mNoIntercept; + private boolean mIsLogContainerSet; protected int mStartContainerType; protected LauncherState mStartState; @@ -180,7 +181,7 @@ public abstract class AbstractStateChangeTouchController /** * Returns the container that the touch started from when leaving NORMAL state. */ - protected abstract int getLogContainerTypeForNormalState(); + protected abstract int getLogContainerTypeForNormalState(MotionEvent ev); private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) { LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState() @@ -231,13 +232,7 @@ public abstract class AbstractStateChangeTouchController @Override public void onDragStart(boolean start) { mStartState = mLauncher.getStateManager().getState(); - if (mStartState == ALL_APPS) { - mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS; - } else if (mStartState == NORMAL) { - mStartContainerType = getLogContainerTypeForNormalState(); - } else if (mStartState == OVERVIEW){ - mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER; - } + mIsLogContainerSet = false; if (mCurrentAnimation == null) { mFromState = mStartState; mToState = null; @@ -285,6 +280,21 @@ public abstract class AbstractStateChangeTouchController return true; } + @Override + public boolean onDrag(float displacement, MotionEvent ev) { + if (!mIsLogContainerSet) { + if (mStartState == ALL_APPS) { + mStartContainerType = LauncherLogProto.ContainerType.ALLAPPS; + } else if (mStartState == NORMAL) { + mStartContainerType = getLogContainerTypeForNormalState(ev); + } else if (mStartState == OVERVIEW) { + mStartContainerType = LauncherLogProto.ContainerType.TASKSWITCHER; + } + mIsLogContainerSet = true; + } + return onDrag(displacement); + } + protected void updateProgress(float fraction) { mCurrentAnimation.setPlayFraction(fraction); if (mAtomicComponentsController != null) { diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java index 2ee0328597..e185a31990 100644 --- a/src/com/android/launcher3/util/MainThreadInitializedObject.java +++ b/src/com/android/launcher3/util/MainThreadInitializedObject.java @@ -18,12 +18,13 @@ package com.android.launcher3.util; import android.content.Context; import android.os.Looper; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.util.ResourceBasedOverride.Overrides; import java.util.concurrent.ExecutionException; -import androidx.annotation.VisibleForTesting; - /** * Utility class for defining singletons which are initiated on main thread. */ @@ -60,6 +61,14 @@ public class MainThreadInitializedObject { mValue = value; } + /** + * Initializes a provider based on resource overrides + */ + public static MainThreadInitializedObject forOverride( + Class clazz, int resourceId) { + return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId)); + } + public interface ObjectProvider { T get(Context context); diff --git a/src/com/android/launcher3/util/PackageUserKey.java b/src/com/android/launcher3/util/PackageUserKey.java index 1ce2822109..aa11968e41 100644 --- a/src/com/android/launcher3/util/PackageUserKey.java +++ b/src/com/android/launcher3/util/PackageUserKey.java @@ -3,6 +3,8 @@ package com.android.launcher3.util; import android.os.UserHandle; import android.service.notification.StatusBarNotification; +import androidx.annotation.Nullable; + import com.android.launcher3.ItemInfo; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -15,7 +17,9 @@ public class PackageUserKey { public UserHandle mUser; private int mHashCode; + @Nullable public static PackageUserKey fromItemInfo(ItemInfo info) { + if (info.getTargetComponent() == null) return null; return new PackageUserKey(info.getTargetComponent().getPackageName(), info.user); } @@ -27,7 +31,7 @@ public class PackageUserKey { update(packageName, user); } - private void update(String packageName, UserHandle user) { + public void update(String packageName, UserHandle user) { mPackageName = packageName; mUser = user; mHashCode = Arrays.hashCode(new Object[] {packageName, user}); @@ -38,7 +42,8 @@ public class PackageUserKey { * @return Whether this PackageUserKey was successfully updated - it shouldn't be used if not. */ public boolean updateFromItemInfo(ItemInfo info) { - if (DeepShortcutManager.supportsShortcuts(info)) { + if (info.getTargetComponent() == null) return false; + if (ShortcutUtil.supportsShortcuts(info)) { update(info.getTargetComponent().getPackageName(), info.user); return true; } diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java new file mode 100644 index 0000000000..af99713a1a --- /dev/null +++ b/src/com/android/launcher3/util/ShortcutUtil.java @@ -0,0 +1,79 @@ +/* + * 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.util; + +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.Utilities; +import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.shortcuts.ShortcutKey; + +public class ShortcutUtil { + /** + * Returns true when we should show shortcut menu for the item. + */ + public static boolean supportsShortcuts(ItemInfo info) { + return isActive(info) && (isApp(info) || isPinnedShortcut(info)); + } + + /** + * Returns true when we should show depp shortcuts in shortcut menu for the item. + */ + public static boolean supportsDeepShortcuts(ItemInfo info) { + return isActive(info) && isApp(info); + } + + /** + * Returns the shortcut id if the item is a pinned shortcut. + */ + public static String getShortcutIdIfPinnedShortcut(ItemInfo info) { + return isActive(info) && isPinnedShortcut(info) + ? ShortcutKey.fromItemInfo(info).getId() : null; + } + + /** + * Returns the person keys associated with the item. (Has no function right now.) + */ + public static String[] getPersonKeysIfPinnedShortcut(ItemInfo info) { + return isActive(info) && isPinnedShortcut(info) + ? ((WorkspaceItemInfo) info).getPersonKeys() : Utilities.EMPTY_STRING_ARRAY; + } + + /** + * Returns true if the item is a deep shortcut. + */ + public static boolean isDeepShortcut(ItemInfo info) { + return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && info instanceof WorkspaceItemInfo; + } + + private static boolean isActive(ItemInfo info) { + boolean isLoading = info instanceof WorkspaceItemInfo + && ((WorkspaceItemInfo) info).hasPromiseIconUi(); + return !isLoading && !info.isDisabled() && !FeatureFlags.GO_DISABLE_WIDGETS; + } + + private static boolean isApp(ItemInfo info) { + return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + } + + private static boolean isPinnedShortcut(ItemInfo info) { + return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT + && info.container != ItemInfo.NO_ID + && info instanceof WorkspaceItemInfo; + } +} \ No newline at end of file diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index f948beb8d2..a4518bae3c 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -153,15 +153,15 @@ public abstract class AbstractSlideInView extends AbstractFloatingView } protected void handleClose(boolean animate, long defaultDuration) { - if (mIsOpen && !animate) { + if (!mIsOpen) { + return; + } + if (!animate) { mOpenCloseAnimator.cancel(); setTranslationShift(TRANSLATION_SHIFT_CLOSED); onCloseComplete(); return; } - if (!mIsOpen) { - return; - } mOpenCloseAnimator.setValues( PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED)); mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() { diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 799762d8fe..c08b659313 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -29,6 +29,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; +import android.util.Log; import android.util.Property; import android.view.MotionEvent; import android.view.View; @@ -41,6 +42,7 @@ import android.widget.FrameLayout; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.Utilities; +import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.TouchController; @@ -261,6 +263,10 @@ public abstract class BaseDragLayer } case ACTION_CANCEL: case ACTION_UP: + if (TestProtocol.sDebugTracing) { + Log.d(TestProtocol.NO_DRAG_TO_WORKSPACE, + "BaseDragLayer.ACTION_UP/CANCEL " + ev); + } mTouchDispatchState &= ~TOUCH_DISPATCHING_GESTURE; mTouchDispatchState &= ~TOUCH_DISPATCHING_VIEW; break; diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index e09a9e8bfc..f728a67764 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -18,7 +18,6 @@ package com.android.launcher3.views; import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; import static com.android.launcher3.Utilities.getBadge; import static com.android.launcher3.Utilities.getFullDrawable; -import static com.android.launcher3.Utilities.isRtl; import static com.android.launcher3.Utilities.mapToRange; import static com.android.launcher3.anim.Interpolators.LINEAR; import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; @@ -564,11 +563,6 @@ public class FloatingIconView extends View implements */ private void checkIconResult(View originalView, boolean isOpening) { CancellationSignal cancellationSignal = new CancellationSignal(); - if (!isOpening) { - // Hide immediately since the floating view starts at a different location. - originalView.setVisibility(INVISIBLE); - cancellationSignal.setOnCancelListener(() -> originalView.setVisibility(VISIBLE)); - } if (mIconLoadResult == null) { Log.w(TAG, "No icon load result found in checkIconResult"); @@ -580,7 +574,7 @@ public class FloatingIconView extends View implements setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); if (isOpening) { - originalView.setVisibility(INVISIBLE); + hideOriginalView(originalView); } } else { mIconLoadResult.onIconLoaded = () -> { @@ -591,15 +585,26 @@ public class FloatingIconView extends View implements setIcon(originalView, mIconLoadResult.drawable, mIconLoadResult.badge, mIconLoadResult.iconOffset); - // Delay swapping views until the icon is loaded to prevent a flash. setVisibility(VISIBLE); - originalView.setVisibility(INVISIBLE); + if (isOpening) { + // Delay swapping views until the icon is loaded to prevent a flash. + hideOriginalView(originalView); + } }; mLoadIconSignal = cancellationSignal; } } } + private void hideOriginalView(View originalView) { + if (originalView instanceof BubbleTextView) { + ((BubbleTextView) originalView).setIconVisible(false); + ((BubbleTextView) originalView).setForceHideDot(true); + } else { + originalView.setVisibility(INVISIBLE); + } + } + private void setBackgroundDrawableBounds(float scale) { sTmpRect.set(mFinalDrawableBounds); Utilities.scaleRectAboutCenter(sTmpRect, scale); @@ -716,7 +721,7 @@ public class FloatingIconView extends View implements */ @UiThread public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { - IconLoadResult result = new IconLoadResult(); + IconLoadResult result = new IconLoadResult(info); new Handler(LauncherModel.getWorkerLooper()).postAtFrontOfQueue(() -> { RectF position = new RectF(); getLocationBoundsForView(l, v, isOpening, position); @@ -745,10 +750,13 @@ public class FloatingIconView extends View implements // Get the drawable on the background thread boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal; - view.mIconLoadResult = sIconLoadResult; - if (shouldLoadIcon && view.mIconLoadResult == null) { - view.mIconLoadResult = fetchIcon(launcher, originalView, - (ItemInfo) originalView.getTag(), isOpening); + if (shouldLoadIcon) { + if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) { + view.mIconLoadResult = sIconLoadResult; + } else { + view.mIconLoadResult = fetchIcon(launcher, originalView, + (ItemInfo) originalView.getTag(), isOpening); + } } sIconLoadResult = null; @@ -776,7 +784,12 @@ public class FloatingIconView extends View implements if (hideOriginal) { if (isOpening) { - originalView.setVisibility(VISIBLE); + if (originalView instanceof BubbleTextView) { + ((BubbleTextView) originalView).setIconVisible(true); + ((BubbleTextView) originalView).setForceHideDot(false); + } else { + originalView.setVisibility(VISIBLE); + } view.finish(dragLayer); } else { view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer); @@ -804,38 +817,33 @@ public class FloatingIconView extends View implements } }); - if (mBadge != null && !(mOriginalIcon instanceof FolderIcon)) { + if (mBadge != null) { ObjectAnimator badgeFade = ObjectAnimator.ofInt(mBadge, DRAWABLE_ALPHA, 255); badgeFade.addUpdateListener(valueAnimator -> invalidate()); fade.play(badgeFade); } - if (originalView instanceof BubbleTextView) { - BubbleTextView btv = (BubbleTextView) originalView; - btv.forceHideDot(true); + if (originalView instanceof IconLabelDotView) { + IconLabelDotView view = (IconLabelDotView) originalView; fade.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - btv.forceHideDot(false); + view.setIconVisible(true); + view.setForceHideDot(false); } }); } - if (originalView instanceof FolderIcon) { - FolderIcon folderIcon = (FolderIcon) originalView; - folderIcon.setBackgroundVisible(false); - folderIcon.getFolderName().setTextVisibility(false); - fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true)); + if (originalView instanceof BubbleTextView) { + BubbleTextView btv = (BubbleTextView) originalView; fade.addListener(new AnimatorListenerAdapter() { @Override - public void onAnimationEnd(Animator animation) { - folderIcon.setBackgroundVisible(true); - if (folderIcon.hasDot()) { - folderIcon.animateDotScale(0, 1f); - } + public void onAnimationStart(Animator animation) { + btv.setIconVisible(true); } }); - } else { + fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255)); + } else if (!(originalView instanceof FolderIcon)) { fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f)); } @@ -890,10 +898,15 @@ public class FloatingIconView extends View implements } private static class IconLoadResult { + final ItemInfo itemInfo; Drawable drawable; Drawable badge; int iconOffset; Runnable onIconLoaded; boolean isIconLoaded; + + public IconLoadResult(ItemInfo itemInfo) { + this.itemInfo = itemInfo; + } } } diff --git a/src/com/android/launcher3/views/IconLabelDotView.java b/src/com/android/launcher3/views/IconLabelDotView.java new file mode 100644 index 0000000000..057caafe78 --- /dev/null +++ b/src/com/android/launcher3/views/IconLabelDotView.java @@ -0,0 +1,24 @@ +/* + * 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.views; + +/** + * A view that has an icon, label, and notification dot. + */ +public interface IconLabelDotView { + void setIconVisible(boolean visible); + void setForceHideDot(boolean hide); +} diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index 63f742768f..465df448e3 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -18,10 +18,8 @@ package com.android.launcher3.views; import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR; import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.graphics.RectF; import android.text.TextUtils; @@ -33,6 +31,9 @@ import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + import com.android.launcher3.Launcher; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -46,7 +47,6 @@ import com.android.launcher3.widget.WidgetsFullSheet; import java.util.ArrayList; import java.util.List; -import androidx.annotation.VisibleForTesting; /** * Popup shown on long pressing an empty space in launcher @@ -169,16 +169,17 @@ public class OptionsPopupView extends ArrowPopup } public static boolean onWidgetsClicked(View view) { - return openWidgets(Launcher.getLauncher(view.getContext())); + return openWidgets(Launcher.getLauncher(view.getContext())) != null; } - public static boolean openWidgets(Launcher launcher) { + /** Returns WidgetsFullSheet that was opened, or null if nothing was opened. */ + @Nullable + public static WidgetsFullSheet openWidgets(Launcher launcher) { if (launcher.getPackageManager().isSafeMode()) { Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show(); - return false; + return null; } else { - WidgetsFullSheet.show(launcher, true /* animated */); - return true; + return WidgetsFullSheet.show(launcher, true /* animated */); } } diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index c36011745c..da1df3f899 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -18,14 +18,14 @@ package com.android.launcher3.views; import static android.content.Context.ACCESSIBILITY_SERVICE; import static android.view.MotionEvent.ACTION_DOWN; +import static androidx.core.graphics.ColorUtils.compositeColors; + import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.anim.Interpolators.ACCEL; import static com.android.launcher3.anim.Interpolators.DEACCEL; import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound; -import static androidx.core.graphics.ColorUtils.compositeColors; - import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.Keyframe; @@ -47,6 +47,13 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; +import androidx.customview.widget.ExploreByTouchHelper; + import com.android.launcher3.DeviceProfile; import com.android.launcher3.Insettable; import com.android.launcher3.Launcher; @@ -62,15 +69,10 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.util.MultiValueAlpha; import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; import com.android.launcher3.util.Themes; +import com.android.launcher3.widget.WidgetsFullSheet; import java.util.List; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.view.ViewCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; -import androidx.customview.widget.ExploreByTouchHelper; /** * Simple scrim which draws a flat color @@ -325,7 +327,7 @@ public class ScrimView extends View implements Insettable, OnChangeListener, if (enabled) { stateManager.addStateListener(this); - handleStateChangedComplete(mLauncher.getStateManager().getState()); + handleStateChangedComplete(stateManager.getState()); } else { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } @@ -437,7 +439,24 @@ public class ScrimView extends View implements Insettable, OnChangeListener, } else if (action == WALLPAPERS) { return OptionsPopupView.startWallpaperPicker(ScrimView.this); } else if (action == WIDGETS) { - return OptionsPopupView.onWidgetsClicked(ScrimView.this); + int originalImportanceForAccessibility = getImportantForAccessibility(); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher); + if (widgetsFullSheet == null) { + setImportantForAccessibility(originalImportanceForAccessibility); + return false; + } + widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View view) {} + + @Override + public void onViewDetachedFromWindow(View view) { + setImportantForAccessibility(originalImportanceForAccessibility); + widgetsFullSheet.removeOnAttachStateChangeListener(this); + } + }); + return true; } else if (action == SETTINGS) { return OptionsPopupView.startSettings(ScrimView.this); } diff --git a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java index 6b6f70d7b6..09b1890491 100644 --- a/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java +++ b/src_shortcuts_overrides/com/android/launcher3/shortcuts/DeepShortcutManager.java @@ -27,9 +27,7 @@ import android.os.Bundle; import android.os.UserHandle; import android.util.Log; -import com.android.launcher3.ItemInfo; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.WorkspaceItemInfo; +import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; @@ -63,13 +61,6 @@ public class DeepShortcutManager { mLauncherApps = (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE); } - public static boolean supportsShortcuts(ItemInfo info) { - boolean isItemPromise = info instanceof WorkspaceItemInfo - && ((WorkspaceItemInfo) info).hasPromiseIconUi(); - return info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION - && !info.isDisabled() && !isItemPromise; - } - public boolean wasLastCallSuccess() { return mWasLastCallSuccess; } @@ -89,8 +80,9 @@ public class DeepShortcutManager { * Gets all the manifest and dynamic shortcuts associated with the given package and user, * to be displayed in the shortcuts container on long press. */ - public List queryForShortcutsContainer(ComponentName activity, + public List queryForShortcutsContainer(@Nullable ComponentName activity, UserHandle user) { + if (activity == null) return Collections.EMPTY_LIST; return query(ShortcutQuery.FLAG_MATCH_MANIFEST | ShortcutQuery.FLAG_MATCH_DYNAMIC, activity.getPackageName(), activity, null, user); } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java index e9dc800e3c..bd6ea502e4 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/AllAppsSwipeController.java @@ -57,7 +57,7 @@ public class AllAppsSwipeController extends AbstractStateChangeTouchController { } @Override - protected int getLogContainerTypeForNormalState() { + protected int getLogContainerTypeForNormalState(MotionEvent ev) { return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE; } diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java new file mode 100644 index 0000000000..60f12d82a7 --- /dev/null +++ b/src_ui_overrides/com/android/launcher3/uioverrides/TogglableFlag.java @@ -0,0 +1,35 @@ +/* + * 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.uioverrides; + +import android.content.Context; +import com.android.launcher3.config.BaseFlags.BaseTogglableFlag; + +public class TogglableFlag extends BaseTogglableFlag { + + public TogglableFlag(String key, boolean defaultValue, String description) { + super(key, defaultValue, description); + } + + @Override + public boolean getOverridenDefaultValue(boolean value) { + return value; + } + + @Override + public void addChangeListener(Context context, Runnable r) { } +} diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java index 5cc64dc9aa..467ae02d5c 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/UiFactory.java @@ -17,9 +17,11 @@ package com.android.launcher3.uioverrides; import android.app.Activity; +import android.app.Person; import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.CancellationSignal; @@ -27,6 +29,7 @@ import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState.ScaleAndTranslation; import com.android.launcher3.LauncherStateManager.StateHandler; +import com.android.launcher3.Utilities; import com.android.launcher3.graphics.RotationMode; import com.android.launcher3.util.TouchController; @@ -95,4 +98,7 @@ public class UiFactory { public static void clearSwipeSharedState(boolean finishAnimation) {} + public static Person[] getPersons(ShortcutInfo si) { + return Utilities.EMPTY_PERSON_ARRAY; + } } diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml index 61c7306e57..c6f55a7178 100644 --- a/tests/AndroidManifest-common.xml +++ b/tests/AndroidManifest-common.xml @@ -20,6 +20,9 @@ + + + diff --git a/tests/OWNERS b/tests/OWNERS index 046d871163..02e8ebcaba 100644 --- a/tests/OWNERS +++ b/tests/OWNERS @@ -1 +1,4 @@ vadimt@google.com +sunnygoyal@google.com +winsonc@google.com +hyunyoungs@google.com diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java new file mode 100644 index 0000000000..efbd9c99c3 --- /dev/null +++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java @@ -0,0 +1,113 @@ +/* + * 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.compat; + +import android.content.pm.PackageInstaller.SessionParams; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.text.TextUtils; + +import androidx.test.filters.LargeTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherState; +import com.android.launcher3.Workspace; +import com.android.launcher3.ui.AbstractLauncherUiTest; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.UUID; + + +/** + * Test to verify promise icon flow. + */ +@LargeTest +@RunWith(AndroidJUnit4.class) +public class PromiseIconUiTest extends AbstractLauncherUiTest { + + private int mSessionId = -1; + + @Override + public void setUp() throws Exception { + super.setUp(); + mDevice.pressHome(); + waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null); + waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL); + mSessionId = -1; + } + + @After + public void tearDown() { + if (mSessionId > -1) { + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + } + } + + /** + * Create a session and return the id. + */ + private int createSession(String label, Bitmap icon) throws Throwable { + SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL); + params.setAppPackageName("test.promise.app"); + params.setAppLabel(label); + params.setAppIcon(icon); + params.setInstallReason(PackageManager.INSTALL_REASON_USER); + return mTargetContext.getPackageManager().getPackageInstaller().createSession(params); + } + + @Test + public void testPromiseIcon_addedFromEligibleSession() throws Throwable { + final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); + final Workspace.ItemOperator findPromiseApp = (info, view) -> + info != null && TextUtils.equals(info.title, appLabel); + + // Create and add test session + mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8)); + + // Verify promise icon is added + waitForLauncherCondition("Test Promise App not found on workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) != null); + + // Remove session + mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId); + mSessionId = -1; + + // Verify promise icon is removed + waitForLauncherCondition("Test Promise App not removed from workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) == null); + } + + @Test + public void testPromiseIcon_notAddedFromIneligibleSession() throws Throwable { + final String appLabel = "Test Promise App " + UUID.randomUUID().toString(); + final Workspace.ItemOperator findPromiseApp = (info, view) -> + info != null && TextUtils.equals(info.title, appLabel); + + // Create and add test session without icon or label + mSessionId = createSession(null, null); + + // Sleep for duration of animation if a view was to be added + some buffer time. + Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500); + + // Verify promise icon is not added + waitForLauncherCondition("Test Promise App not found on workspace", launcher -> + launcher.getWorkspace().getFirstMatch(findPromiseApp) == null); + } +} diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index 8dc8cea40d..dc890bb005 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import static java.lang.System.exit; -import android.app.Instrumentation; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -38,6 +37,7 @@ import android.util.Log; import androidx.test.InstrumentationRegistry; import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiDevice; import androidx.test.uiautomator.Until; @@ -111,6 +111,7 @@ public abstract class AbstractLauncherUiTest { launcher -> checkLauncherIntegrity(launcher, containerType))); } + mLauncher.enableDebugTracing(); } protected final LauncherActivityRule mActivityMonitor = new LauncherActivityRule(); @@ -186,14 +187,6 @@ public abstract class AbstractLauncherUiTest { } } - protected void lockRotation(boolean naturalOrientation) throws RemoteException { - if (naturalOrientation) { - mDevice.setOrientationNatural(); - } else { - mDevice.setOrientationRight(); - } - } - protected void clearLauncherData() throws IOException, InterruptedException { if (TestHelpers.isInLauncherProcess()) { LauncherSettings.Settings.call(mTargetContext.getContentResolver(), @@ -201,6 +194,7 @@ public abstract class AbstractLauncherUiTest { resetLoaderState(); } else { clearPackageData(mDevice.getLauncherPackageName()); + mLauncher.enableDebugTracing(); } } @@ -272,6 +266,12 @@ public abstract class AbstractLauncherUiTest { waitForLauncherCondition(message, condition, DEFAULT_ACTIVITY_TIMEOUT); } + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide + // flakiness. + protected T getOnceNotNull(String message, Function f) { + return getOnceNotNull(message, f, DEFAULT_ACTIVITY_TIMEOUT); + } + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide // flakiness. protected void waitForLauncherCondition( @@ -280,6 +280,20 @@ public abstract class AbstractLauncherUiTest { Wait.atMost(message, () -> getFromLauncher(condition), timeout); } + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide + // flakiness. + protected T getOnceNotNull(String message, Function f, long timeout) { + if (!TestHelpers.isInLauncherProcess()) return null; + + final Object[] output = new Object[1]; + Wait.atMost(message, () -> { + final Object fromLauncher = getFromLauncher(f); + output[0] = fromLauncher; + return fromLauncher != null; + }, timeout); + return (T) output[0]; + } + // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide // flakiness. protected void waitForLauncherCondition( @@ -330,30 +344,27 @@ public abstract class AbstractLauncherUiTest { } protected void startAppFast(String packageName) { - final Instrumentation instrumentation = getInstrumentation(); - final Intent intent = instrumentation.getContext().getPackageManager(). - getLaunchIntentForPackage(packageName); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - instrumentation.getTargetContext().startActivity(intent); - assertTrue(packageName + " didn't start", - mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), DEFAULT_UI_TIMEOUT)); + startIntent( + getInstrumentation().getContext().getPackageManager().getLaunchIntentForPackage( + packageName), + By.pkg(packageName).depth(0)); } protected void startTestActivity(int activityNumber) { final String packageName = getAppPackageName(); - final Instrumentation instrumentation = getInstrumentation(); - final Intent intent = instrumentation.getContext().getPackageManager(). + final Intent intent = getInstrumentation().getContext().getPackageManager(). getLaunchIntentForPackage(packageName); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName(packageName, "com.android.launcher3.tests.Activity" + activityNumber)); - instrumentation.getTargetContext().startActivity(intent); - assertTrue(packageName + " didn't start", - mDevice.wait( - Until.hasObject(By.pkg(packageName).text("TestActivity" + activityNumber)), - DEFAULT_UI_TIMEOUT)); + startIntent(intent, By.pkg(packageName).text("TestActivity" + activityNumber)); + } + + private void startIntent(Intent intent, BySelector selector) { + intent.addCategory(Intent.CATEGORY_LAUNCHER); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + getInstrumentation().getTargetContext().startActivity(intent); + assertTrue("App didn't start: " + selector, + mDevice.wait(Until.hasObject(selector), DEFAULT_UI_TIMEOUT)); } public static String resolveSystemApp(String category) { diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java index ddcb4da883..80bb3edddd 100644 --- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java +++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java @@ -44,8 +44,11 @@ class PortraitLandscapeRunner implements TestRule { } finally { mTest.mDevice.setOrientationNatural(); mTest.executeOnLauncher(launcher -> - launcher.getRotationHelper().forceAllowRotationForTesting( - false)); + { + if (launcher != null) { + launcher.getRotationHelper().forceAllowRotationForTesting(false); + } + }); mTest.mLauncher.setExpectedRotation(Surface.ROTATION_0); } } diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java index 4dab44fadd..c2a3c1c524 100644 --- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java +++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java @@ -190,7 +190,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { launcher -> assertTrue("ensureScrollable didn't make workspace scrollable", isWorkspaceScrollable(launcher))); assertNotNull("ensureScrollable didn't add Chrome app", - workspace.tryGetWorkspaceAppIcon("Chrome")); + workspace.getWorkspaceAppIcon("Chrome")); // Test flinging workspace. workspace.flingBackward(); @@ -206,7 +206,7 @@ public class TaplTestsLauncher3 extends AbstractLauncherUiTest { assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL)); // Test starting a workspace app. - final AppIcon app = workspace.tryGetWorkspaceAppIcon("Chrome"); + final AppIcon app = workspace.getWorkspaceAppIcon("Chrome"); assertNotNull("No Chrome app in workspace", app); } diff --git a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java index 3206a69bbf..3f35a3a73a 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java @@ -69,34 +69,24 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { } @Test + @PortraitLandscape + @org.junit.Ignore public void testWidgetConfig() throws Throwable { - runTest(false, true); - } - - @Test - @Ignore // b/121280703 - public void testWidgetConfig_rotate() throws Throwable { - runTest(true, true); + runTest(true); } @Test + @PortraitLandscape + @org.junit.Ignore public void testConfigCancelled() throws Throwable { - runTest(false, false); + runTest(false); } - @Test - @Ignore // b/121280703 - public void testConfigCancelled_rotate() throws Throwable { - runTest(true, false); - } /** - * @param rotateConfig should the config screen be rotated * @param acceptConfig accept the config activity */ - private void runTest(boolean rotateConfig, boolean acceptConfig) throws Throwable { - lockRotation(true); - + private void runTest(boolean acceptConfig) throws Throwable { clearHomescreen(); mDevice.pressHome(); @@ -110,13 +100,6 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest { // Widget id for which the config activity was opened mWidgetId = monitor.getWidgetId(); - if (rotateConfig) { - // Rotate the screen and verify that the config activity is recreated - monitor = new WidgetConfigStartupMonitor(); - lockRotation(false); - assertEquals(mWidgetId, monitor.getWidgetId()); - } - // Verify that the widget id is valid and bound assertNotNull(mAppWidgetManager.getAppWidgetInfo(mWidgetId)); diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index 276c6144a2..1edce22ec6 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -15,6 +15,9 @@ */ package com.android.launcher3.ui.widget; +import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName; + +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import androidx.test.filters.LargeTest; @@ -22,6 +25,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.launcher3.LauncherAppWidgetInfo; import com.android.launcher3.LauncherAppWidgetProviderInfo; +import com.android.launcher3.tapl.Widget; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.rule.ShellCommandRule; @@ -41,19 +45,9 @@ public class AddWidgetTest extends AbstractLauncherUiTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind(); @Test - public void testDragIcon_portrait() throws Throwable { - lockRotation(true); - performTest(); - } - - @Test - @Ignore // b/121280703 - public void testDragIcon_landscape() throws Throwable { - lockRotation(false); - performTest(); - } - - private void performTest() throws Throwable { + @PortraitLandscape + @org.junit.Ignore + public void testDragIcon() throws Throwable { clearHomescreen(); mDevice.pressHome(); @@ -70,5 +64,10 @@ public class AddWidgetTest extends AbstractLauncherUiTest { (info, view) -> info instanceof LauncherAppWidgetInfo && ((LauncherAppWidgetInfo) info).providerName.getClassName().equals( widgetInfo.provider.getClassName())).call()); + + final Widget widget = mLauncher.getWorkspace().tryGetWidget(widgetInfo.label, + DEFAULT_UI_TIMEOUT); + assertNotNull("Widget not found on the workspace", widget); + widget.launch(getAppPackageName()); } } diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java index 3a7df64e81..e6348d9c02 100644 --- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java @@ -44,6 +44,7 @@ import com.android.launcher3.tapl.Workspace; import com.android.launcher3.ui.AbstractLauncherUiTest; import com.android.launcher3.ui.TestViewHelpers; import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.widget.PendingAddWidgetInfo; import com.android.launcher3.widget.WidgetHostViewLoader; @@ -54,7 +55,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; /** * Tests for bind widget flow. @@ -326,9 +329,12 @@ public class BindWidgetTest extends AbstractLauncherUiTest { int count = 0; String pkg = invalidPackage; - Set activePackage = getOnUiThread(() -> - PackageInstallerCompat.getInstance(mTargetContext) - .updateAndGetActiveSessionCache().keySet()); + Set activePackage = getOnUiThread(() -> { + Set packages = new HashSet<>(); + PackageInstallerCompat.getInstance(mTargetContext).updateAndGetActiveSessionCache() + .keySet().forEach(packageUserKey -> packages.add(packageUserKey.mPackageName)); + return packages; + }); while(true) { try { mTargetContext.getPackageManager().getPackageInfo( diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java index a9a5090988..07129ddd95 100644 --- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java +++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java @@ -128,8 +128,6 @@ public class RequestPinItemTest extends AbstractLauncherUiTest { if (!Utilities.ATLEAST_OREO) { return; } - lockRotation(true); - clearHomescreen(); mDevice.pressHome(); diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java index 593cce832b..899686bd61 100644 --- a/tests/src/com/android/launcher3/util/Wait.java +++ b/tests/src/com/android/launcher3/util/Wait.java @@ -1,6 +1,7 @@ package com.android.launcher3.util; import android.os.SystemClock; +import android.util.Log; import org.junit.Assert; @@ -16,7 +17,9 @@ public class Wait { } public static void atMost(String message, Condition condition, long timeout, long sleepMillis) { - long endTime = SystemClock.uptimeMillis() + timeout; + final long startTime = SystemClock.uptimeMillis(); + long endTime = startTime + timeout; + Log.d("Wait", "atMost: " + startTime + " - " + endTime); while (SystemClock.uptimeMillis() < endTime) { try { if (condition.isTrue()) { @@ -36,6 +39,7 @@ public class Wait { } catch (Throwable t) { throw new RuntimeException(t); } + Log.d("Wait", "atMost: timed out: " + SystemClock.uptimeMillis()); Assert.fail(message); } } diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java index eef2f24baa..cdda0f0dcc 100644 --- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java +++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java @@ -15,17 +15,16 @@ import java.io.IOException; public class FailureWatcher extends TestWatcher { private static final String TAG = "FailureWatcher"; - private static int sScreenshotCount = 0; final private UiDevice mDevice; public FailureWatcher(UiDevice device) { mDevice = device; } - private void dumpViewHierarchy() { + private static void dumpViewHierarchy(UiDevice device) { final ByteArrayOutputStream stream = new ByteArrayOutputStream(); try { - mDevice.dumpWindowHierarchy(stream); + device.dumpWindowHierarchy(stream); stream.flush(); stream.close(); for (String line : stream.toString().split("\\r?\\n")) { @@ -38,22 +37,27 @@ public class FailureWatcher extends TestWatcher { @Override protected void failed(Throwable e, Description description) { - if (mDevice == null) return; + onError(mDevice, description, e); + } + + public static void onError(UiDevice device, Description description, Throwable e) { + if (device == null) return; final String pathname = getInstrumentation().getTargetContext(). - getFilesDir().getPath() + "/TaplTestScreenshot" + sScreenshotCount++ + ".png"; + getFilesDir().getPath() + "/TestScreenshot-" + description.getMethodName() + + ".png"; Log.e(TAG, "Failed test " + description.getMethodName() + ", screenshot will be saved to " + pathname + ", track trace is below, UI object dump is further below:\n" + Log.getStackTraceString(e)); - dumpViewHierarchy(); + dumpViewHierarchy(device); try { - final String dumpsysResult = mDevice.executeShellCommand( + final String dumpsysResult = device.executeShellCommand( "dumpsys activity service TouchInteractionService"); Log.d(TAG, "TouchInteractionService: " + dumpsysResult); } catch (IOException ex) { } - mDevice.takeScreenshot(new File(pathname)); + device.takeScreenshot(new File(pathname)); } } diff --git a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java index 204240324e..62fe26d13b 100644 --- a/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java +++ b/tests/src/com/android/launcher3/util/rule/LauncherActivityRule.java @@ -19,6 +19,7 @@ import android.app.Activity; import android.app.Application; import android.app.Application.ActivityLifecycleCallbacks; import android.os.Bundle; + import androidx.test.InstrumentationRegistry; import com.android.launcher3.Launcher; @@ -84,19 +85,27 @@ public class LauncherActivityRule implements TestRule { } @Override - public void onActivityStarted(Activity activity) { } + public void onActivityStarted(Activity activity) { + if (activity instanceof Launcher) { + mActivity.getRotationHelper().forceAllowRotationForTesting(true); + } + } @Override - public void onActivityResumed(Activity activity) { } + public void onActivityResumed(Activity activity) { + } @Override - public void onActivityPaused(Activity activity) { } + public void onActivityPaused(Activity activity) { + } @Override - public void onActivityStopped(Activity activity) { } + public void onActivityStopped(Activity activity) { + } @Override - public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { } + public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { + } @Override public void onActivityDestroyed(Activity activity) { diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java index 9ff354a7eb..f070280ea2 100644 --- a/tests/tapl/com/android/launcher3/tapl/AllApps.java +++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java @@ -120,7 +120,7 @@ public class AllApps extends LauncherInstrumentation.VisibleContainer { mLauncher.assertTrue("Unable to scroll to a clickable icon: " + appName, hasClickableIcon(allAppsContainer, appListRecycler, appIconSelector)); - final UiObject2 appIcon = mLauncher.getObjectInContainer(appListRecycler, + final UiObject2 appIcon = mLauncher.waitForObjectInContainer(appListRecycler, appIconSelector); return new AppIcon(mLauncher, appIcon); } diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java index bbd2c29e3e..25e6e8c8f7 100644 --- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java +++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java @@ -74,7 +74,7 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer { flingForward(); } - mLauncher.getObjectInContainer(verifyActiveContainer(), clearAllSelector).click(); + mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector).click(); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer( "dismissed all tasks")) { return new Workspace(mLauncher); diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java index 82af7b03ac..df80a51fd7 100644 --- a/tests/tapl/com/android/launcher3/tapl/Launchable.java +++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java @@ -16,6 +16,8 @@ package com.android.launcher3.tapl; +import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; + import android.graphics.Point; import androidx.test.uiautomator.By; @@ -23,13 +25,10 @@ import androidx.test.uiautomator.BySelector; import androidx.test.uiautomator.UiObject2; import androidx.test.uiautomator.Until; -import com.android.launcher3.testing.TestProtocol; - /** * Ancestor for AppIcon and AppMenuItem. */ abstract class Launchable { - private static final int WAIT_TIME_MS = 60000; protected final LauncherInstrumentation mLauncher; protected final UiObject2 mObject; @@ -53,9 +52,12 @@ abstract class Launchable { private Background launch(BySelector selector) { LauncherInstrumentation.log("Launchable.launch before click " + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds()); - mLauncher.assertTrue( - "Launching an app didn't open a new window: " + mObject.getText(), - mObject.clickAndWait(Until.newWindow(), WAIT_TIME_MS)); + + mLauncher.executeAndWaitForEvent( + () -> mObject.click(), + event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, + "Launching an app didn't open a new window: " + mObject.getText()); + mLauncher.assertTrue( "App didn't start: " + selector, mLauncher.getDevice().wait(Until.hasObject(selector), diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index fe7401ca62..15615fc6b6 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -124,7 +124,7 @@ public final class LauncherInstrumentation { private static final String APPS_RES_ID = "apps_view"; private static final String OVERVIEW_RES_ID = "overview_panel"; private static final String WIDGETS_RES_ID = "widgets_list_view"; - public static final int WAIT_TIME_MS = 60000; + public static final int WAIT_TIME_MS = 10000; private static final String SYSTEMUI_PACKAGE = "com.android.systemui"; private static WeakReference sActiveContainer = new WeakReference<>(null); @@ -178,6 +178,7 @@ public final class LauncherInstrumentation { PackageManager pm = getContext().getPackageManager(); ProviderInfo pi = pm.resolveContentProvider( testProviderAuthority, MATCH_ALL | MATCH_DISABLED_COMPONENTS); + assertNotNull("Cannot find content provider for " + testProviderAuthority, pi); ComponentName cn = new ComponentName(pi.packageName, pi.name); if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) { @@ -367,7 +368,7 @@ public final class LauncherInstrumentation { } } - private void assertEquals(String message, String expected, String actual) { + void assertEquals(String message, String expected, String actual) { if (!TextUtils.equals(expected, actual)) { fail(message + " expected: '" + expected + "' but was: '" + actual + "'"); } @@ -677,13 +678,6 @@ public final class LauncherInstrumentation { return object; } - @NonNull - UiObject2 getObjectInContainer(UiObject2 container, BySelector selector) { - final UiObject2 object = container.findObject(selector); - assertNotNull("Can't find an object with selector: " + selector, object); - return object; - } - @NonNull List getObjectsInContainer(UiObject2 container, String resName) { return container.findObjects(getLauncherObjectSelector(resName)); @@ -769,8 +763,7 @@ public final class LauncherInstrumentation { final Bundle parcel = (Bundle) executeAndWaitForEvent( () -> linearGesture(startX, startY, endX, endY, steps), event -> TestProtocol.SWITCHED_TO_STATE_MESSAGE.equals(event.getClassName()), - "Swipe failed to receive an event for the swipe end: " + startX + ", " + startY - + ", " + endX + ", " + endY); + "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))); @@ -888,6 +881,7 @@ public final class LauncherInstrumentation { } long movePointer(long downTime, long startTime, long duration, Point from, Point to) { + log("movePointer: " + from + " to " + to); final Point point = new Point(); long steps = duration / GESTURE_STEP_MS; long currentTime = startTime; @@ -962,4 +956,17 @@ public final class LauncherInstrumentation { public void disableDebugTracing() { getTestInfo(TestProtocol.REQUEST_DISABLE_DEBUG_TRACING); } + + public int getTotalPssKb() { + return getTestInfo(TestProtocol.REQUEST_TOTAL_PSS_KB). + getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD); + } + + public void produceJavaLeak() { + getTestInfo(TestProtocol.REQUEST_JAVA_LEAK); + } + + public void produceNativeLeak() { + getTestInfo(TestProtocol.REQUEST_NATIVE_LEAK); + } } \ No newline at end of file diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java index 6e3332260b..91f0fc4c83 100644 --- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java +++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java @@ -16,18 +16,16 @@ package com.android.launcher3.tapl; +import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED; + import android.graphics.Rect; import androidx.test.uiautomator.UiObject2; -import androidx.test.uiautomator.Until; - -import com.android.launcher3.testing.TestProtocol; /** * A recent task in the overview panel carousel. */ public final class OverviewTask { - private static final long WAIT_TIME_MS = 60000; private final LauncherInstrumentation mLauncher; private final UiObject2 mTask; private final BaseOverview mOverview; @@ -66,9 +64,11 @@ public final class OverviewTask { verifyActiveContainer(); try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( "clicking an overview task")) { - mLauncher.assertTrue("Launching task didn't open a new window: " + - mTask.getParent().getContentDescription(), - mTask.clickAndWait(Until.newWindow(), WAIT_TIME_MS)); + mLauncher.executeAndWaitForEvent( + () -> mTask.click(), + event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED, + "Launching task didn't open a new window: " + + mTask.getParent().getContentDescription()); } return new Background(mLauncher); } diff --git a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java index 399c59d36d..a089a527ef 100644 --- a/tests/tapl/com/android/launcher3/tapl/TestHelpers.java +++ b/tests/tapl/com/android/launcher3/tapl/TestHelpers.java @@ -109,7 +109,7 @@ public class TestHelpers { DropBoxManager.Entry entry; StringBuilder errorDetails = new StringBuilder(); while (null != (entry = dropbox.getNextEntry(label, timestamp))) { - if (errorDetails.length() != 0) errorDetails.append("------------------------------"); + errorDetails.append("------------------------------\n"); timestamp = entry.getTimeMillis(); errorDetails.append(new Date(timestamp)); errorDetails.append(": "); diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java index 2495933ac6..7d308afb99 100644 --- a/tests/tapl/com/android/launcher3/tapl/Widgets.java +++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java @@ -19,6 +19,7 @@ package com.android.launcher3.tapl; import static org.junit.Assert.fail; import android.graphics.Point; +import android.graphics.Rect; import androidx.test.uiautomator.By; import androidx.test.uiautomator.BySelector; @@ -31,7 +32,8 @@ import com.android.launcher3.ResourceUtils; * All widgets container. */ public final class Widgets extends LauncherInstrumentation.VisibleContainer { - private static final int FLING_SPEED = 1500; + private static final Rect MARGINS = new Rect(100, 100, 100, 100); + private static final int FLING_STEPS = 10; Widgets(LauncherInstrumentation launcher) { super(launcher); @@ -46,11 +48,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { "want to fling forward in widgets")) { LauncherInstrumentation.log("Widgets.flingForward enter"); final UiObject2 widgetsContainer = verifyActiveContainer(); - widgetsContainer.setGestureMargins(0, 0, 0, - ResourceUtils.getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, - mLauncher.getResources()) + 1); - widgetsContainer.fling(Direction.DOWN, - (int) (FLING_SPEED * mLauncher.getDisplayDensity())); + mLauncher.scroll(widgetsContainer, Direction.DOWN, 1f, MARGINS, FLING_STEPS); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung forward")) { verifyActiveContainer(); } @@ -66,10 +64,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { "want to fling backwards in widgets")) { LauncherInstrumentation.log("Widgets.flingBackward enter"); final UiObject2 widgetsContainer = verifyActiveContainer(); - widgetsContainer.setGestureMargin(100); - widgetsContainer.fling(Direction.UP, - (int) (FLING_SPEED * mLauncher.getDisplayDensity())); - mLauncher.waitForIdle(); + mLauncher.scroll(widgetsContainer, Direction.UP, 1f, MARGINS, FLING_STEPS); try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) { verifyActiveContainer(); } @@ -82,7 +77,7 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { return LauncherInstrumentation.ContainerType.WIDGETS; } - public Widget getWidget(String label) { + public Widget getWidget(String labelText) { final int margin = ResourceUtils.getNavbarSize( ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources()) + 1; final UiObject2 widgetsContainer = verifyActiveContainer(); @@ -91,17 +86,24 @@ public final class Widgets extends LauncherInstrumentation.VisibleContainer { final Point displaySize = mLauncher.getRealDisplaySize(); int i = 0; - final BySelector selector = By. - clazz("com.android.launcher3.widget.WidgetCell"). - hasDescendant(By.text(label)); + final BySelector selector = By.clazz("android.widget.TextView").text(labelText); for (; ; ) { - final UiObject2 widget = mLauncher.tryWaitForLauncherObject(selector, 300); - if (widget != null && widget.getVisibleBounds().bottom <= displaySize.y - margin) { - return new Widget(mLauncher, widget); + final UiObject2 label = mLauncher.tryWaitForLauncherObject(selector, 300); + if (label != null) { + final UiObject2 widget = label.getParent().getParent(); + mLauncher.assertEquals( + "View is not WidgetCell", + "com.android.launcher3.widget.WidgetCell", + widget.getClassName()); + + if (widget.getVisibleBounds().bottom <= displaySize.y - margin) { + return new Widget(mLauncher, widget); + } } + if (++i > 40) fail("Too many attempts"); - widgetsContainer.scroll(Direction.DOWN, 1f); + mLauncher.scroll(widgetsContainer, Direction.DOWN, 0.7f, MARGINS, 50); } } } diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java index 07f8b64433..510ea14091 100644 --- a/tests/tapl/com/android/launcher3/tapl/Workspace.java +++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java @@ -21,6 +21,7 @@ import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL; import static junit.framework.TestCase.assertTrue; import android.graphics.Point; +import android.graphics.Rect; import android.os.SystemClock; import android.view.KeyEvent; import android.view.MotionEvent; @@ -40,6 +41,7 @@ public final class Workspace extends Home { private static final float FLING_SPEED = LauncherInstrumentation.isAvd() ? 1500.0F : 3500.0F; private static final int DRAG_DURACTION = 2000; + private static final int FLING_STEPS = 10; private final UiObject2 mHotseat; Workspace(LauncherInstrumentation launcher) { @@ -108,10 +110,13 @@ public final class Workspace extends Home { */ @NonNull public AppIcon getWorkspaceAppIcon(String appName) { - return new AppIcon(mLauncher, - mLauncher.getObjectInContainer( - verifyActiveContainer(), - AppIcon.getAppIconSelector(appName, mLauncher))); + try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer( + "want to get a workspace icon")) { + return new AppIcon(mLauncher, + mLauncher.waitForObjectInContainer( + verifyActiveContainer(), + AppIcon.getAppIconSelector(appName, mLauncher))); + } } /** @@ -142,13 +147,13 @@ public final class Workspace extends Home { @NonNull public AppIcon getHotseatAppIcon(String appName) { - return new AppIcon(mLauncher, mLauncher.getObjectInContainer( + return new AppIcon(mLauncher, mLauncher.waitForObjectInContainer( mHotseat, AppIcon.getAppIconSelector(appName, mLauncher))); } @NonNull public Folder getHotseatFolder(String appName) { - return new Folder(mLauncher, mLauncher.getObjectInContainer( + return new Folder(mLauncher, mLauncher.waitForObjectInContainer( mHotseat, Folder.getSelector(appName, mLauncher))); } @@ -177,9 +182,9 @@ public final class Workspace extends Home { */ public void flingForward() { final UiObject2 workspace = verifyActiveContainer(); - workspace.setGestureMargins(0, 0, mLauncher.getEdgeSensitivityWidth(), 0); - workspace.fling(Direction.RIGHT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); - mLauncher.waitForIdle(); + mLauncher.scroll(workspace, Direction.RIGHT, 1f, + new Rect(0, 0, mLauncher.getEdgeSensitivityWidth(), 0), + FLING_STEPS); verifyActiveContainer(); } @@ -189,9 +194,9 @@ public final class Workspace extends Home { */ public void flingBackward() { final UiObject2 workspace = verifyActiveContainer(); - workspace.setGestureMargins(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0); - workspace.fling(Direction.LEFT, (int) (FLING_SPEED * mLauncher.getDisplayDensity())); - mLauncher.waitForIdle(); + mLauncher.scroll(workspace, Direction.LEFT, 1f, + new Rect(mLauncher.getEdgeSensitivityWidth(), 0, 0, 0), + FLING_STEPS); verifyActiveContainer(); }