From 54fa110bba4f6b07af4c4eab01ec2a9bce070474 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 7 Dec 2022 22:48:37 -0800 Subject: [PATCH] Removing some unnecessary interfaces and merging them to ActivityContext Bug: 259733681 Test: Everything compiles Change-Id: If6b530f6e589a851cf8554f5de0849843bd66dd4 --- .../appprediction/PredictionRowView.java | 3 +- .../launcher3/taskbar/BaseTaskbarContext.java | 6 +- .../uioverrides/states/AllAppsState.java | 7 +- src/com/android/launcher3/BaseActivity.java | 6 +- src/com/android/launcher3/DeviceProfile.java | 30 -- src/com/android/launcher3/LauncherState.java | 7 +- .../allapps/ActivityAllAppsContainerView.java | 7 +- .../allapps/BaseAllAppsContainerView.java | 5 +- .../search/DefaultSearchAdapterProvider.java | 6 +- .../graphics/LauncherPreviewRenderer.java | 7 + .../launcher3/statemanager/BaseState.java | 4 +- .../launcher3/views/ActivityContext.java | 259 ++++++++++++++++-- .../android/launcher3/views/AppLauncher.java | 213 -------------- .../launcher3/views/OptionsPopupView.java | 4 +- .../uioverrides/states/AllAppsState.java | 4 +- .../util/ActivityContextWrapper.java | 11 + 16 files changed, 277 insertions(+), 302 deletions(-) delete mode 100644 src/com/android/launcher3/views/AppLauncher.java diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java index c54d119904..4fbe8cfd26 100644 --- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java +++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java @@ -30,7 +30,6 @@ import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -51,7 +50,7 @@ import java.util.List; import java.util.stream.Collectors; @TargetApi(Build.VERSION_CODES.P) -public class PredictionRowView +public class PredictionRowView extends LinearLayout implements OnDeviceProfileChangeListener, FloatingHeaderRow { private final T mActivityContext; diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java index b4052e3d71..82d18307ca 100644 --- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java @@ -19,18 +19,16 @@ import android.content.Context; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.util.Themes; -import com.android.launcher3.views.AppLauncher; +import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; import java.util.List; // TODO(b/218912746): Share more behavior to avoid all apps context depending directly on taskbar. /** Base for common behavior between taskbar window contexts. */ -public abstract class BaseTaskbarContext extends ContextThemeWrapper implements AppLauncher, - DeviceProfileListenable { +public abstract class BaseTaskbarContext extends ContextThemeWrapper implements ActivityContext { protected final LayoutInflater mLayoutInflater; private final List mDPChangeListeners = new ArrayList<>(); diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java index fd184c63a4..2a42175b5b 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -20,12 +20,11 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAP import android.content.Context; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.ActivityContext; /** * Definition for AllApps state @@ -40,7 +39,7 @@ public class AllAppsState extends LauncherState { } @Override - public + public int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) { return isToState ? context.getDeviceProfile().allAppsOpenDuration @@ -78,7 +77,7 @@ public class AllAppsState extends LauncherState { } @Override - protected + protected float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) { if (context.getDeviceProfile().isTablet) { return context.getDeviceProfile().bottomSheetDepth; diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java index 9bdc822570..61707dff49 100644 --- a/src/com/android/launcher3/BaseActivity.java +++ b/src/com/android/launcher3/BaseActivity.java @@ -30,14 +30,13 @@ import android.window.OnBackInvokedDispatcher; import androidx.annotation.IntDef; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.util.SystemUiController; import com.android.launcher3.util.ViewCache; -import com.android.launcher3.views.AppLauncher; +import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.ScrimView; import java.io.PrintWriter; @@ -48,8 +47,7 @@ import java.util.List; /** * Launcher BaseActivity */ -public abstract class BaseActivity extends Activity implements AppLauncher, - DeviceProfileListenable { +public abstract class BaseActivity extends Activity implements ActivityContext { private static final String TAG = "BaseActivity"; diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 828066a1b0..ce78fce8c0 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -55,7 +55,6 @@ import com.android.launcher3.util.DisplayController.Info; import com.android.launcher3.util.WindowBounds; import java.io.PrintWriter; -import java.util.List; import java.util.Locale; @SuppressLint("NewApi") @@ -1631,35 +1630,6 @@ public class DeviceProfile { void onDeviceProfileChanged(DeviceProfile dp); } - /** Allows registering listeners for {@link DeviceProfile} changes. */ - public interface DeviceProfileListenable { - - /** The current device profile. */ - DeviceProfile getDeviceProfile(); - - /** Registered {@link OnDeviceProfileChangeListener} instances. */ - List getOnDeviceProfileChangeListeners(); - - /** Notifies listeners of a {@link DeviceProfile} change. */ - default void dispatchDeviceProfileChanged() { - DeviceProfile deviceProfile = getDeviceProfile(); - List listeners = getOnDeviceProfileChangeListeners(); - for (int i = listeners.size() - 1; i >= 0; i--) { - listeners.get(i).onDeviceProfileChanged(deviceProfile); - } - } - - /** Register listener for {@link DeviceProfile} changes. */ - default void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { - getOnDeviceProfileChangeListeners().add(listener); - } - - /** Unregister listener for {@link DeviceProfile} changes. */ - default void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { - getOnDeviceProfileChangeListeners().remove(listener); - } - } - /** * Handler that deals with ItemInfo of the views for the DeviceProfile */ diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java index 31a7d1827f..5dddc6f458 100644 --- a/src/com/android/launcher3/LauncherState.java +++ b/src/com/android/launcher3/LauncherState.java @@ -41,6 +41,7 @@ import com.android.launcher3.states.SpringLoadedState; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.uioverrides.states.AllAppsState; import com.android.launcher3.uioverrides.states.OverviewState; +import com.android.launcher3.views.ActivityContext; import java.util.Arrays; @@ -261,7 +262,7 @@ public abstract class LauncherState implements BaseState { * * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs. */ - public final + public final float getDepth(DEVICE_PROFILE_CONTEXT context) { return getDepth(context, BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode); @@ -272,7 +273,7 @@ public abstract class LauncherState implements BaseState { * * @see #getDepth(Context). */ - public final + public final float getDepth(DEVICE_PROFILE_CONTEXT context, boolean isMultiWindowMode) { if (isMultiWindowMode) { return 0; @@ -280,7 +281,7 @@ public abstract class LauncherState implements BaseState { return getDepthUnchecked(context); } - protected + protected float getDepthUnchecked(DEVICE_PROFILE_CONTEXT context) { return 0f; } diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java index 5c55b53e2a..5296df1d46 100644 --- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java @@ -27,13 +27,12 @@ import android.widget.RelativeLayout; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.RecyclerView; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem; import com.android.launcher3.allapps.search.SearchAdapterProvider; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.views.AppLauncher; +import com.android.launcher3.views.ActivityContext; import java.util.ArrayList; @@ -42,8 +41,8 @@ import java.util.ArrayList; * * @param Type of context inflating all apps. */ -public class ActivityAllAppsContainerView extends BaseAllAppsContainerView { +public class ActivityAllAppsContainerView + extends BaseAllAppsContainerView { private static final long DEFAULT_SEARCH_TRANSITION_DURATION_MS = 300; diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java index f308a254a3..65b4661199 100644 --- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java @@ -48,7 +48,6 @@ import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget.DragObject; @@ -81,8 +80,8 @@ import java.util.stream.Stream; * * @param Type of context inflating all apps. */ -public abstract class BaseAllAppsContainerView extends SpringRelativeLayout implements DragSource, Insettable, +public abstract class BaseAllAppsContainerView + extends SpringRelativeLayout implements DragSource, Insettable, OnDeviceProfileChangeListener, OnActivePageChangedListener, ScrimView.ScrimDrawingController { diff --git a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java index 4fb732d5f0..20edf8a93c 100644 --- a/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java +++ b/src/com/android/launcher3/allapps/search/DefaultSearchAdapterProvider.java @@ -26,17 +26,17 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.allapps.AllAppsGridAdapter; import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.views.AppLauncher; +import com.android.launcher3.views.ActivityContext; /** * Provides views for local search results. */ -public class DefaultSearchAdapterProvider extends SearchAdapterProvider { +public class DefaultSearchAdapterProvider extends SearchAdapterProvider { private final RecyclerView.ItemDecoration mDecoration; private View mHighlightedView; - public DefaultSearchAdapterProvider(AppLauncher launcher) { + public DefaultSearchAdapterProvider(ActivityContext launcher) { super(launcher); mDecoration = new RecyclerView.ItemDecoration() { @Override diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index 772913db8f..e7b0446741 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -63,6 +63,7 @@ import androidx.annotation.Nullable; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.Hotseat; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; @@ -174,6 +175,7 @@ public class LauncherPreviewRenderer extends ContextWrapper } } + private final List mDpChangeListeners = new ArrayList<>(); private final Handler mUiHandler; private final Context mContext; private final InvariantDeviceProfile mIdp; @@ -330,6 +332,11 @@ public class LauncherPreviewRenderer extends ContextWrapper return mDp; } + @Override + public List getOnDeviceProfileChangeListeners() { + return mDpChangeListeners; + } + @Override public Hotseat getHotseat() { return mHotseat; diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java index 32378b8384..239042550a 100644 --- a/src/com/android/launcher3/statemanager/BaseState.java +++ b/src/com/android/launcher3/statemanager/BaseState.java @@ -18,7 +18,7 @@ package com.android.launcher3.statemanager; import android.content.Context; import com.android.launcher3.DeviceProfile; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; +import com.android.launcher3.views.ActivityContext; /** * Interface representing a state of a StatefulActivity @@ -37,7 +37,7 @@ public interface BaseState { /** * @return How long the animation to this state should take (or from this state to NORMAL). */ - + int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState); /** diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index dd5b22eb84..28085e19fc 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -17,23 +17,41 @@ package com.android.launcher3.views; import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_KEYBOARD_CLOSED; +import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; +import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; +import android.app.ActivityOptions; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; +import android.content.pm.LauncherApps; import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.os.IBinder; +import android.os.Process; +import android.os.StrictMode; +import android.os.UserHandle; +import android.util.Log; +import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.WindowInsets; import android.view.WindowInsetsController; import android.view.inputmethod.InputMethodManager; +import android.widget.Toast; import androidx.annotation.Nullable; +import com.android.launcher3.BubbleTextView; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.ActivityAllAppsContainerView; import com.android.launcher3.allapps.search.SearchAdapterProvider; @@ -41,20 +59,30 @@ import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.logger.LauncherAtom; +import com.android.launcher3.logging.InstanceId; +import com.android.launcher3.logging.InstanceIdSequence; import com.android.launcher3.logging.StatsLogManager; import com.android.launcher3.model.StringCache; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.popup.PopupDataProvider; +import com.android.launcher3.util.ActivityOptionsWrapper; import com.android.launcher3.util.OnboardingPrefs; +import com.android.launcher3.util.PackageManagerHelper; import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.ViewCache; +import java.util.List; + /** * An interface to be used along with a context for various activities in Launcher. This allows a * generic class to depend on Context subclass instead of an Activity. */ public interface ActivityContext { + String TAG = "ActivityContext"; + default boolean finishAutoCancelActionMode() { return false; } @@ -117,6 +145,28 @@ public interface ActivityContext { DeviceProfile getDeviceProfile(); + /** Registered {@link OnDeviceProfileChangeListener} instances. */ + List getOnDeviceProfileChangeListeners(); + + /** Notifies listeners of a {@link DeviceProfile} change. */ + default void dispatchDeviceProfileChanged() { + DeviceProfile deviceProfile = getDeviceProfile(); + List listeners = getOnDeviceProfileChangeListeners(); + for (int i = listeners.size() - 1; i >= 0; i--) { + listeners.get(i).onDeviceProfileChanged(deviceProfile); + } + } + + /** Register listener for {@link DeviceProfile} changes. */ + default void addOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { + getOnDeviceProfileChangeListeners().add(listener); + } + + /** Unregister listener for {@link DeviceProfile} changes. */ + default void removeOnDeviceProfileChangeListener(OnDeviceProfileChangeListener listener) { + getOnDeviceProfileChangeListeners().remove(listener); + } + default ViewCache getViewCache() { return new ViewCache(); } @@ -162,32 +212,6 @@ public interface ActivityContext { return false; } - /** - * Returns the ActivityContext associated with the given Context, or throws an exception if - * the Context is not associated with any ActivityContext. - */ - static T lookupContext(Context context) { - T activityContext = lookupContextNoThrow(context); - if (activityContext == null) { - throw new IllegalArgumentException("Cannot find ActivityContext in parent tree"); - } - return activityContext; - } - - /** - * Returns the ActivityContext associated with the given Context, or null if - * the Context is not associated with any ActivityContext. - */ - static T lookupContextNoThrow(Context context) { - if (context instanceof ActivityContext) { - return (T) context; - } else if (context instanceof ContextWrapper) { - return lookupContextNoThrow(((ContextWrapper) context).getBaseContext()); - } else { - return null; - } - } - default View.OnClickListener getItemOnClickListener() { return v -> { // No op. @@ -256,4 +280,187 @@ public interface ActivityContext { }); } } + + /** + * Safely starts an activity. + * + * @param v View starting the activity. + * @param intent Base intent being launched. + * @param item Item associated with the view. + * @return {@code true} if the activity starts successfully. + */ + default boolean startActivitySafely( + View v, Intent intent, @Nullable ItemInfo item) { + + Context context = (Context) this; + if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) { + Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); + return false; + } + + Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null; + UserHandle user = item == null ? null : item.user; + + // Prepare intent + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (v != null) { + intent.setSourceBounds(Utilities.getViewBounds(v)); + } + try { + boolean isShortcut = (item instanceof WorkspaceItemInfo) + && (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT + || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) + && !((WorkspaceItemInfo) item).isPromise(); + if (isShortcut) { + // Shortcuts need some special checks due to legacy reasons. + startShortcutIntentSafely(intent, optsBundle, item); + } else if (user == null || user.equals(Process.myUserHandle())) { + // Could be launching some bookkeeping activity + context.startActivity(intent, optsBundle); + } else { + context.getSystemService(LauncherApps.class).startMainActivity( + intent.getComponent(), user, intent.getSourceBounds(), optsBundle); + } + if (item != null) { + InstanceId instanceId = new InstanceIdSequence().newInstanceId(); + logAppLaunch(getStatsLogManager(), item, instanceId); + } + return true; + } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { + Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); + Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); + } + return false; + } + + /** Returns {@code true} if an app launch is blocked due to safe mode. */ + default boolean isAppBlockedForSafeMode() { + return false; + } + + /** + * Creates and logs a new app launch event. + */ + default void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info, + InstanceId instanceId) { + statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId) + .log(LAUNCHER_APP_LAUNCH_TAP); + } + + /** + * Returns launch options for an Activity. + * + * @param v View initiating a launch. + * @param item Item associated with the view. + */ + default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { + int left = 0, top = 0; + int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); + if (v instanceof BubbleTextView) { + // Launch from center of icon, not entire view + Drawable icon = ((BubbleTextView) v).getIcon(); + if (icon != null) { + Rect bounds = icon.getBounds(); + left = (width - bounds.width()) / 2; + top = v.getPaddingTop(); + width = bounds.width(); + height = bounds.height(); + } + } + ActivityOptions options = + ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); + + options.setLaunchDisplayId( + (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() + : Display.DEFAULT_DISPLAY); + RunnableList callback = new RunnableList(); + return new ActivityOptionsWrapper(options, callback); + } + + /** + * Safely launches an intent for a shortcut. + * + * @param intent Intent to start. + * @param optsBundle Optional launch arguments. + * @param info Shortcut information. + */ + default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { + try { + StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); + try { + // Temporarily disable deathPenalty on all default checks. For eg, shortcuts + // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure + // is enabled by default on NYC. + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() + .penaltyLog().build()); + + if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + String id = ((WorkspaceItemInfo) info).getDeepShortcutId(); + String packageName = intent.getPackage(); + startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user); + } else { + // Could be launching some bookkeeping activity + ((Context) this).startActivity(intent, optsBundle); + } + } finally { + StrictMode.setVmPolicy(oldPolicy); + } + } catch (SecurityException e) { + if (!onErrorStartingShortcut(intent, info)) { + throw e; + } + } + } + + /** + * A wrapper around the platform method with Launcher specific checks. + */ + default void startShortcut(String packageName, String id, Rect sourceBounds, + Bundle startActivityOptions, UserHandle user) { + if (GO_DISABLE_WIDGETS) { + return; + } + try { + ((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id, + sourceBounds, startActivityOptions, user); + } catch (SecurityException | IllegalStateException e) { + Log.e(TAG, "Failed to start shortcut", e); + } + } + + /** + * Invoked when a shortcut fails to launch. + * @param intent Shortcut intent that failed to start. + * @param info Shortcut information. + * @return {@code true} if the error is handled by this callback. + */ + default boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { + return false; + } + + /** + * Returns the ActivityContext associated with the given Context, or throws an exception if + * the Context is not associated with any ActivityContext. + */ + static T lookupContext(Context context) { + T activityContext = lookupContextNoThrow(context); + if (activityContext == null) { + throw new IllegalArgumentException("Cannot find ActivityContext in parent tree"); + } + return activityContext; + } + + /** + * Returns the ActivityContext associated with the given Context, or null if + * the Context is not associated with any ActivityContext. + */ + static T lookupContextNoThrow(Context context) { + if (context instanceof ActivityContext) { + return (T) context; + } else if (context instanceof ContextWrapper) { + return lookupContextNoThrow(((ContextWrapper) context).getBaseContext()); + } else { + return null; + } + } } diff --git a/src/com/android/launcher3/views/AppLauncher.java b/src/com/android/launcher3/views/AppLauncher.java deleted file mode 100644 index 19e66abd78..0000000000 --- a/src/com/android/launcher3/views/AppLauncher.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2022 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; - -import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP; -import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS; - -import android.app.ActivityOptions; -import android.content.ActivityNotFoundException; -import android.content.Context; -import android.content.Intent; -import android.content.pm.LauncherApps; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Process; -import android.os.StrictMode; -import android.os.UserHandle; -import android.util.Log; -import android.view.Display; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.Nullable; - -import com.android.launcher3.BubbleTextView; -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.logging.InstanceId; -import com.android.launcher3.logging.InstanceIdSequence; -import com.android.launcher3.logging.StatsLogManager; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.model.data.WorkspaceItemInfo; -import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.util.PackageManagerHelper; -import com.android.launcher3.util.RunnableList; - -/** An {@link ActivityContext} that can also launch app activities and shortcuts safely. */ -public interface AppLauncher extends ActivityContext { - - String TAG = "AppLauncher"; - - /** - * Safely starts an activity. - * - * @param v View starting the activity. - * @param intent Base intent being launched. - * @param item Item associated with the view. - * @return {@code true} if the activity starts successfully. - */ - default boolean startActivitySafely( - View v, Intent intent, @Nullable ItemInfo item) { - - Context context = (Context) this; - if (isAppBlockedForSafeMode() && !PackageManagerHelper.isSystemApp(context, intent)) { - Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show(); - return false; - } - - Bundle optsBundle = (v != null) ? getActivityLaunchOptions(v, item).toBundle() : null; - UserHandle user = item == null ? null : item.user; - - // Prepare intent - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (v != null) { - intent.setSourceBounds(Utilities.getViewBounds(v)); - } - try { - boolean isShortcut = (item instanceof WorkspaceItemInfo) - && (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT - || item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) - && !((WorkspaceItemInfo) item).isPromise(); - if (isShortcut) { - // Shortcuts need some special checks due to legacy reasons. - startShortcutIntentSafely(intent, optsBundle, item); - } else if (user == null || user.equals(Process.myUserHandle())) { - // Could be launching some bookkeeping activity - context.startActivity(intent, optsBundle); - } else { - context.getSystemService(LauncherApps.class).startMainActivity( - intent.getComponent(), user, intent.getSourceBounds(), optsBundle); - } - if (item != null) { - InstanceId instanceId = new InstanceIdSequence().newInstanceId(); - logAppLaunch(getStatsLogManager(), item, instanceId); - } - return true; - } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { - Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); - Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); - } - return false; - } - - /** Returns {@code true} if an app launch is blocked due to safe mode. */ - default boolean isAppBlockedForSafeMode() { - return false; - } - - /** - * Creates and logs a new app launch event. - */ - default void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info, - InstanceId instanceId) { - statsLogManager.logger().withItemInfo(info).withInstanceId(instanceId) - .log(LAUNCHER_APP_LAUNCH_TAP); - } - - /** - * Returns launch options for an Activity. - * - * @param v View initiating a launch. - * @param item Item associated with the view. - */ - default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) { - int left = 0, top = 0; - int width = v.getMeasuredWidth(), height = v.getMeasuredHeight(); - if (v instanceof BubbleTextView) { - // Launch from center of icon, not entire view - Drawable icon = ((BubbleTextView) v).getIcon(); - if (icon != null) { - Rect bounds = icon.getBounds(); - left = (width - bounds.width()) / 2; - top = v.getPaddingTop(); - width = bounds.width(); - height = bounds.height(); - } - } - ActivityOptions options = - ActivityOptions.makeClipRevealAnimation(v, left, top, width, height); - - options.setLaunchDisplayId( - (v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId() - : Display.DEFAULT_DISPLAY); - RunnableList callback = new RunnableList(); - return new ActivityOptionsWrapper(options, callback); - } - - /** - * Safely launches an intent for a shortcut. - * - * @param intent Intent to start. - * @param optsBundle Optional launch arguments. - * @param info Shortcut information. - */ - default void startShortcutIntentSafely(Intent intent, Bundle optsBundle, ItemInfo info) { - try { - StrictMode.VmPolicy oldPolicy = StrictMode.getVmPolicy(); - try { - // Temporarily disable deathPenalty on all default checks. For eg, shortcuts - // containing file Uri's would cause a crash as penaltyDeathOnFileUriExposure - // is enabled by default on NYC. - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll() - .penaltyLog().build()); - - if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - String id = ((WorkspaceItemInfo) info).getDeepShortcutId(); - String packageName = intent.getPackage(); - startShortcut(packageName, id, intent.getSourceBounds(), optsBundle, info.user); - } else { - // Could be launching some bookkeeping activity - ((Context) this).startActivity(intent, optsBundle); - } - } finally { - StrictMode.setVmPolicy(oldPolicy); - } - } catch (SecurityException e) { - if (!onErrorStartingShortcut(intent, info)) { - throw e; - } - } - } - - /** - * A wrapper around the platform method with Launcher specific checks. - */ - default void startShortcut(String packageName, String id, Rect sourceBounds, - Bundle startActivityOptions, UserHandle user) { - if (GO_DISABLE_WIDGETS) { - return; - } - try { - ((Context) this).getSystemService(LauncherApps.class).startShortcut(packageName, id, - sourceBounds, startActivityOptions, user); - } catch (SecurityException | IllegalStateException e) { - Log.e(TAG, "Failed to start shortcut", e); - } - } - - /** - * Invoked when a shortcut fails to launch. - * @param intent Shortcut intent that failed to start. - * @param info Shortcut information. - * @return {@code true} if the error is handled by this callback. - */ - default boolean onErrorStartingShortcut(Intent intent, ItemInfo info) { - return false; - } -} diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index 622516f33f..5b57e22d81 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -139,12 +139,12 @@ public class OptionsPopupView extends ArrowPopup mTargetRect.roundOut(outPos); } - public static OptionsPopupView show(AppLauncher launcher, RectF targetRect, + public static OptionsPopupView show(ActivityContext launcher, RectF targetRect, List items, boolean shouldAddArrow) { return show(launcher, targetRect, items, shouldAddArrow, 0 /* width */); } - public static OptionsPopupView show(AppLauncher launcher, RectF targetRect, + public static OptionsPopupView show(ActivityContext launcher, RectF targetRect, List items, boolean shouldAddArrow, int width) { OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater() .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false); diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java index a581f91f74..772a995324 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java @@ -20,11 +20,11 @@ import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAP import android.content.Context; -import com.android.launcher3.DeviceProfile.DeviceProfileListenable; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.util.Themes; +import com.android.launcher3.views.ActivityContext; /** * Definition for AllApps state @@ -40,7 +40,7 @@ public class AllAppsState extends LauncherState { } @Override - public + public int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) { return isToState ? context.getDeviceProfile().allAppsOpenDuration diff --git a/tests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/src/com/android/launcher3/util/ActivityContextWrapper.java index 2618a2e1f1..191d284939 100644 --- a/tests/src/com/android/launcher3/util/ActivityContextWrapper.java +++ b/tests/src/com/android/launcher3/util/ActivityContextWrapper.java @@ -20,15 +20,21 @@ import android.content.ContextWrapper; import android.view.ContextThemeWrapper; import com.android.launcher3.DeviceProfile; +import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.views.ActivityContext; import com.android.launcher3.views.BaseDragLayer; +import java.util.ArrayList; +import java.util.List; + /** * {@link ContextWrapper} with internal Launcher interface for testing */ public class ActivityContextWrapper extends ContextThemeWrapper implements ActivityContext { + private final List mDpChangeListeners = new ArrayList<>(); + private final DeviceProfile mProfile; private final MyDragLayer mMyDragLayer; @@ -43,6 +49,11 @@ public class ActivityContextWrapper extends ContextThemeWrapper implements Activ return mMyDragLayer; } + @Override + public List getOnDeviceProfileChangeListeners() { + return mDpChangeListeners; + } + @Override public DeviceProfile getDeviceProfile() { return mProfile;