Files
Lawnchair/src/com/android/launcher3/Launcher.java
T
Shamali P 472709d9ca Delete the filter code as it will be provided differently in refactor
With the dagger update the initialization happens in constructor which
we would avoid once we work on refactor and move it into a repository
class. The list will also be fetched from a different data source, so,
makes sense to delete it at the moment.

Bug: 406324964
Flag: EXEMPT BUGFIX
Test: Not applicable as code is removed
Change-Id: Iab8bb1e94677abf9b5b882f4d3485b002faf3e0f
2025-03-27 14:15:05 -07:00

3086 lines
125 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2;
import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
import static com.android.launcher3.Flags.enableStrictMode;
import static com.android.launcher3.Flags.enableWorkspaceInflation;
import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_SHORTCUT;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_PICK_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_CURRENT_SCREEN_IDS;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_ACTIVITY_RESULT;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_ARGS;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_PENDING_REQUEST_CODE;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_WIDGET_PANEL;
import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_COOKIE;
import static com.android.launcher3.LauncherConstants.TraceEvents.COLD_STARTUP_TRACE_METHOD_NAME;
import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_ALL_APPS_TRACE_COOKIE;
import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_ALL_APPS_TRACE_METHOD_NAME;
import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_WORKSPACE_TRACE_COOKIE;
import static com.android.launcher3.LauncherConstants.TraceEvents.DISPLAY_WORKSPACE_TRACE_METHOD_NAME;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_CREATE_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.NO_OFFSET;
import static com.android.launcher3.LauncherState.NO_SCALE;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.Workspace.mapOverCellLayouts;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
import static com.android.launcher3.icons.BitmapRenderer.createHardwareBitmap;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
import static com.android.launcher3.logging.StatsLogManager.EventEnum;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE;
import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION;
import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION;
import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.COLD;
import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.COLD_DEVICE_REBOOTING;
import static com.android.launcher3.logging.StatsLogManager.StatsLatencyLogger.LatencyType.WARM;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.content.ActivityNotFoundException;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.StrictMode;
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.text.TextUtils;
import android.text.method.TextKeyListener;
import android.util.FloatProperty;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.OvershootInterpolator;
import android.widget.Toast;
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.core.os.BuildCompat;
import androidx.window.embedding.RuleController;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.PropertyListBuilder;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellPosMapper;
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.debug.TestEventEmitter;
import com.android.launcher3.debug.TestEventEmitter.TestEvent;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.dragndrop.LauncherDragController;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
import com.android.launcher3.logging.ColdRebootStartupLatencyLogger;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
import com.android.launcher3.logging.StartupLatencyLogger;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.StringCache;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.PinRequestHelper;
import com.android.launcher3.popup.ArrowPopup;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.popup.SystemShortcut;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.states.RotationHelper;
import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.touch.AllAppsSwipeController;
import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.ActivityResultInfo;
import com.android.launcher3.util.BackPressHandler;
import com.android.launcher3.util.CannedAnimationCoordinator;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ContextTracker;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.ItemInflater;
import com.android.launcher3.util.KeyboardShortcutsDelegate;
import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.MSDLPlayerWrapper;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.PendingRequestArgs;
import com.android.launcher3.util.PluginManagerWrapper;
import com.android.launcher3.util.RunnableList;
import com.android.launcher3.util.ScreenOnTracker;
import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
import com.android.launcher3.util.SettingsCache;
import com.android.launcher3.util.StableViewInfo;
import com.android.launcher3.util.SystemUiController;
import com.android.launcher3.util.Themes;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.TraceHelper;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingSurfaceView;
import com.android.launcher3.views.OptionsPopupView;
import com.android.launcher3.views.ScrimView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
import com.android.launcher3.widget.LauncherWidgetHolder;
import com.android.launcher3.widget.PendingAddShortcutInfo;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.PendingAppWidgetHostView;
import com.android.launcher3.widget.WidgetAddFlowHandler;
import com.android.launcher3.widget.WidgetManagerHelper;
import com.android.launcher3.widget.custom.CustomWidgetManager;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
import com.android.launcher3.widget.util.WidgetSizes;
import com.android.systemui.plugins.LauncherOverlayPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.shared.LauncherOverlayManager;
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Default launcher application.
*/
public class Launcher extends StatefulActivity<LauncherState>
implements Callbacks, InvariantDeviceProfile.OnIDPChangeListener,
PluginListener<LauncherOverlayPlugin> {
public static final String TAG = "Launcher";
public static final ContextTracker.ActivityTracker<Launcher> ACTIVITY_TRACKER =
new ContextTracker.ActivityTracker<>();
static final boolean LOGD = false;
static final boolean DEBUG_STRICT_MODE = false;
private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
/**
* IntentStarter uses request codes starting with this. This must be greater than all activity
* request codes used internally.
*/
protected static final int REQUEST_LAST = 100;
public static final String INTENT_ACTION_ALL_APPS_TOGGLE =
"launcher.intent_action_all_apps_toggle";
private static boolean sIsNewProcess = true;
private StateManager<LauncherState, Launcher> mStateManager;
private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
// How long to wait before the new-shortcut animation automatically pans the workspace
@VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
@Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
private static final FloatProperty<Workspace<?>> WORKSPACE_WIDGET_SCALE =
WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
private static final FloatProperty<Hotseat> HOTSEAT_WIDGET_SCALE =
HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WIDGET_TRANSITION);
private final ModelCallbacks mModelCallbacks = createModelCallbacks();
private final KeyboardShortcutsDelegate mKeyboardShortcutsDelegate =
new KeyboardShortcutsDelegate(this);
@Thunk
Workspace<?> mWorkspace;
@Thunk
DragLayer mDragLayer;
private WidgetManagerHelper mAppWidgetManager;
private LauncherWidgetHolder mAppWidgetHolder;
private ItemInflater<Launcher> mItemInflater;
private final int[] mTmpAddItemCellCoordinates = new int[2];
@Thunk
Hotseat mHotseat;
private DropTargetBar mDropTargetBar;
// Main container view for the all apps screen.
@Thunk
ActivityAllAppsContainerView<Launcher> mAppsView;
AllAppsTransitionController mAllAppsController;
// Scrim view for the all apps and overview state.
@Thunk
ScrimView mScrimView;
// UI and state for the overview panel
private View mOverviewPanel;
// Used to notify when an activity launch has been deferred because launcher is not yet resumed
// TODO: See if we can remove this later
private Runnable mOnDeferredActivityLaunchCallback;
private OnPreDrawListener mOnInitialBindListener;
private LauncherModel mModel;
private ModelWriter mModelWriter;
private IconCache mIconCache;
private LauncherAccessibilityDelegate mAccessibilityDelegate;
private PopupDataProvider mPopupDataProvider;
private WidgetPickerDataProvider mWidgetPickerDataProvider;
// We only want to get the SharedPreferences once since it does an FS stat each time we get
// it from the context.
private SharedPreferences mSharedPrefs;
// Activity result which needs to be processed after workspace has loaded.
private ActivityResultInfo mPendingActivityResult;
/**
* Holds extra information required to handle a result from an external call, like
* {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)}
*/
private PendingRequestArgs mPendingRequestArgs;
// Request id for any pending activity result
protected int mPendingActivityRequestCode = -1;
private ViewGroupFocusHelper mFocusHandler;
private RotationHelper mRotationHelper;
protected LauncherOverlayManager mOverlayManager;
protected LauncherDragController mDragController;
// If true, overlay callbacks are deferred
private boolean mDeferOverlayCallbacks;
private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
protected long mLastTouchUpTime = -1;
private boolean mTouchInProgress;
// New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions.
// When Launcher is not in AllApps state mAllAppsSessionLogId will be null.
// User actions within AllApps state are logged with this InstanceId, to recreate AllApps
// session on the server side.
protected InstanceId mAllAppsSessionLogId;
private LauncherState mPrevLauncherState;
private StartupLatencyLogger mStartupLatencyLogger;
private CellPosMapper mCellPosMapper = CellPosMapper.DEFAULT;
private final CannedAnimationCoordinator mAnimationCoordinator =
new CannedAnimationCoordinator(this);
private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
private boolean mIsColdStartupAfterReboot;
private boolean mIsNaturalScrollingEnabled;
private final SettingsCache.OnChangeListener mNaturalScrollingChangedListener =
enabled -> mIsNaturalScrollingEnabled = enabled;
public static Launcher getLauncher(Context context) {
return fromContext(context);
}
@Override
@TargetApi(Build.VERSION_CODES.S)
protected void onCreate(Bundle savedInstanceState) {
mStartupLatencyLogger = createStartupLatencyLogger(
sIsNewProcess
? LockedUserState.get(this).isUserUnlockedAtLauncherStartup()
? COLD
: COLD_DEVICE_REBOOTING
: WARM);
mIsColdStartupAfterReboot = sIsNewProcess
&& !LockedUserState.get(this).isUserUnlockedAtLauncherStartup();
if (mIsColdStartupAfterReboot) {
/*
* This trace is used to calculate the time from create to the point that icons are
* visible.
*/
Trace.beginAsyncSection(
COLD_STARTUP_TRACE_METHOD_NAME, COLD_STARTUP_TRACE_COOKIE);
}
sIsNewProcess = false;
mStartupLatencyLogger
.logStart(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.logStart(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
// Only use a hard-coded cookie since we only want to trace this once.
Trace.beginAsyncSection(
DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
if (DEBUG_STRICT_MODE
|| (FeatureFlags.IS_STUDIO_BUILD && enableStrictMode())) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectActivityLeaks()
.penaltyLog()
.penaltyDeath()
.build());
}
if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.NOTIFY_CRASHES.get()) {
final String notificationChannelId = "com.android.launcher3.Debug";
final String notificationChannelName = "Debug";
final String notificationTag = "Debug";
final int notificationId = 0;
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(new NotificationChannel(
notificationChannelId, notificationChannelName,
NotificationManager.IMPORTANCE_HIGH));
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
String stackTrace = Log.getStackTraceString(throwable);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, stackTrace);
shareIntent = Intent.createChooser(shareIntent, null);
PendingIntent sharePendingIntent = PendingIntent.getActivity(
this, 0, shareIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE);
Notification notification = new Notification.Builder(this, notificationChannelId)
.setSmallIcon(android.R.drawable.ic_menu_close_clear_cancel)
.setContentTitle("Launcher crash detected!")
.setStyle(new Notification.BigTextStyle().bigText(stackTrace))
.addAction(android.R.drawable.ic_menu_share, "Share", sharePendingIntent)
.build();
notificationManager.notify(notificationTag, notificationId, notification);
Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler =
Thread.getDefaultUncaughtExceptionHandler();
if (defaultUncaughtExceptionHandler != null) {
defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
}
});
}
super.onCreate(savedInstanceState);
setWallpaperDependentTheme(this);
LauncherAppState app = LauncherAppState.getInstance(this);
mModel = app.getModel();
mRotationHelper = new RotationHelper(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
initDeviceProfile(idp);
idp.addOnChangeListener(this);
mSharedPrefs = LauncherPrefs.getPrefs(this);
mIconCache = app.getIconCache();
mAccessibilityDelegate = createAccessibilityDelegate();
initDragController();
mAllAppsController = new AllAppsTransitionController(this);
mStateManager = new StateManager<>(this, NORMAL);
mAppWidgetManager = new WidgetManagerHelper(this);
mAppWidgetHolder = LauncherWidgetHolder.newInstance(this);
mAppWidgetHolder.setAppWidgetRemovedCallback(
appWidgetId -> getWorkspace().removeWidget(appWidgetId));
setupViews();
updateDisallowBack();
mAppWidgetHolder.startListening();
mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
mPopupDataProvider = new PopupDataProvider(this);
mWidgetPickerDataProvider = new WidgetPickerDataProvider();
PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
if (internalStateHandled) {
if (savedInstanceState != null) {
// InternalStateHandler has already set the appropriate state.
// We dont need to do anything.
savedInstanceState.remove(RUNTIME_STATE);
}
}
restoreState(savedInstanceState);
mStateManager.reapplyState();
if (savedInstanceState != null) {
int[] pageIds = savedInstanceState.getIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS);
if (pageIds != null) {
mModelCallbacks.setPagesToBindSynchronously(IntSet.wrap(pageIds));
}
}
mStartupLatencyLogger.logWorkspaceLoadStartTime();
if (!mModel.addCallbacksAndLoad(this)) {
if (!internalStateHandled) {
// If we are not binding synchronously, pause drawing until initial bind complete,
// so that the system could continue to show the device loading prompt
mOnInitialBindListener = Boolean.FALSE::booleanValue;
}
}
// For handling default keys
setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
setContentView(getRootView());
if (mOnInitialBindListener != null) {
getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
}
getRootView().dispatchInsets();
final SettingsCache settingsCache = SettingsCache.INSTANCE.get(this);
settingsCache.register(TOUCHPAD_NATURAL_SCROLLING, mNaturalScrollingChangedListener);
mIsNaturalScrollingEnabled = settingsCache.getValue(TOUCHPAD_NATURAL_SCROLLING);
// Listen for screen turning off
ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
mOverlayManager = getDefaultOverlay();
PluginManagerWrapper.INSTANCE.get(this)
.addPluginListener(this, LauncherOverlayPlugin.class);
mRotationHelper.initialize();
TraceHelper.INSTANCE.endSection();
getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
setTitle(R.string.home_screen);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
if (BuildCompat.isAtLeastV()
&& com.android.launcher3.Flags.enableTwoPaneLauncherSettings()) {
RuleController.getInstance(this).setRules(
RuleController.parseRules(this, R.xml.split_configuration));
}
TestEventEmitter.sendEvent(TestEvent.LAUNCHER_ON_CREATE);
}
protected ModelCallbacks createModelCallbacks() {
return new ModelCallbacks(this);
}
/**
* We only log startup latency in {@link COLD_DEVICE_REBOOTING} type. For other latency types,
* create a no op implementation.
*/
private StartupLatencyLogger createStartupLatencyLogger(
StatsLogManager.StatsLatencyLogger.LatencyType latencyType) {
if (latencyType == COLD_DEVICE_REBOOTING) {
return createColdRebootStartupLatencyLogger();
}
return StartupLatencyLogger.Companion.getNO_OP();
}
/**
* Create {@link ColdRebootStartupLatencyLogger} that only collects launcher startup latency
* metrics without sending them anywhere. Child class can override this method to create logger
* that overrides {@link StartupLatencyLogger#log()} to report those metrics.
*/
protected ColdRebootStartupLatencyLogger createColdRebootStartupLatencyLogger() {
return new ColdRebootStartupLatencyLogger();
}
@NonNull View getAccessibilityActionView() {
return findViewById(R.id.accessibility_action_view);
}
/**
* Provide {@link OnBackAnimationCallback} in below order:
* <ol>
* <li> auto cancel action mode handler
* <li> drag handler
* <li> view handler
* <li> registered {@link BackPressHandler}
* <li> state handler
* </ol>
*
* A back gesture (a single click on back button, or a swipe back gesture that contains a series
* of swipe events) should be handled by the same handler from above list. For a new back
* gesture, a new handler should be regenerated.
*
* Note that state handler will always be handling the back press event if the previous 3 don't.
*/
@NonNull
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
protected OnBackAnimationCallback getOnBackAnimationCallback() {
// #1 auto cancel action mode handler
if (isInAutoCancelActionMode()) {
return this::finishAutoCancelActionMode;
}
// #2 drag handler
if (mDragController.isDragging()) {
return mDragController::cancelDrag;
}
// #3 view handler
AbstractFloatingView topView =
AbstractFloatingView.getTopOpenView(Launcher.this);
if (topView != null && topView.canHandleBack()) {
return topView;
}
// #4 Custom back handlers
for (BackPressHandler handler : mBackPressedHandlers) {
if (handler.canHandleBack()) {
return handler;
}
}
// #5 state handler
return new OnBackAnimationCallback() {
@Override
public void onBackStarted(BackEvent backEvent) {
Launcher.this.onBackStarted();
}
@Override
public void onBackInvoked() {
onStateBack();
}
@Override
public void onBackProgressed(@NonNull BackEvent backEvent) {
mStateManager.getState().onBackProgressed(
Launcher.this, backEvent.getProgress());
}
@Override
public void onBackCancelled() {
Launcher.this.onBackCancelled();
}
};
}
protected LauncherOverlayManager getDefaultOverlay() {
return new LauncherOverlayManager() { };
}
@Override
public void onPluginConnected(LauncherOverlayPlugin overlayManager, Context context) {
switchOverlay(() -> overlayManager.createOverlayManager(this));
}
@Override
public void onPluginDisconnected(LauncherOverlayPlugin plugin) {
switchOverlay(this::getDefaultOverlay);
}
private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
if (mOverlayManager != null) {
mOverlayManager.onActivityDestroyed();
}
mOverlayManager = overlaySupplier.get();
if (getRootView().isAttachedToWindow()) {
mOverlayManager.onAttachedToWindow();
}
mDeferOverlayCallbacks = true;
checkIfOverlayStillDeferred();
}
@Override
public void dispatchDeviceProfileChanged() {
super.dispatchDeviceProfileChanged();
mOverlayManager.onDeviceProvideChanged();
}
@Override
public void onEnterAnimationComplete() {
super.onEnterAnimationComplete();
mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
}
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
// Always update device profile when multi window mode changed.
initDeviceProfile(mDeviceProfile.inv);
dispatchDeviceProfileChanged();
}
/**
* Initializes the drag controller.
*/
protected void initDragController() {
mDragController = new LauncherDragController(this);
}
@Override
public void onIdpChanged(boolean modelPropertiesChanged) {
onHandleConfigurationChanged();
}
@Override
protected void onHandleConfigurationChanged() {
Trace.beginSection("Launcher#onHandleconfigurationChanged");
try {
if (!initDeviceProfile(mDeviceProfile.inv)) {
return;
}
dispatchDeviceProfileChanged();
reapplyUi();
mDragLayer.recreateControllers();
// Calling onSaveInstanceState ensures that static cache used by listWidgets is
// initialized properly.
onSaveInstanceState(new Bundle());
mModel.rebindCallbacks();
updateDisallowBack();
} finally {
Trace.endSection();
}
}
private void updateFixedLandscape() {
if (!com.android.launcher3.Flags.oneGridSpecs()) {
return;
}
// When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
// by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets and foldables
// afterwards.
if (getDeviceProfile().isPhone) {
LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
} else if (getDeviceProfile().isTablet) {
// Tablet do not use fixed landscape mode, make sure it can't be activated by mistake
LauncherPrefs.get(this).put(FIXED_LANDSCAPE_MODE, false);
}
getRotationHelper().setFixedLandscape(
Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape
);
}
public void onAssistantVisibilityChanged(float visibility) {
mHotseat.getQsb().setAlpha(1f - visibility);
}
/**
* Returns {@code true} if a new DeviceProfile is initialized, and {@code false} otherwise.
*/
protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
// Load configuration-specific DeviceProfile
DeviceProfile deviceProfile = idp.getDeviceProfile(this);
if (mDeviceProfile == deviceProfile) {
return false;
}
mDeviceProfile = deviceProfile;
if (isInMultiWindowMode()) {
mDeviceProfile = mDeviceProfile.getMultiWindowProfile(
this, getMultiWindowDisplaySize());
}
if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
} else {
mCellPosMapper = new CellPosMapper(mDeviceProfile.isVerticalBarLayout(),
mDeviceProfile.numShownHotseatIcons);
}
mModelWriter = mModel.getWriter(true, mCellPosMapper, this);
updateFixedLandscape();
return true;
}
/**
* Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
* a configuration step, this allows the proper animations to run after other transitions.
*/
private int completeAdd(
int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
CellPos cellPos = getCellPosMapper().mapModelToPresenter(info);
int screenId = cellPos.screenId;
if (info.container == CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank) we make sure
// that the drop page actually exists.
screenId = ensurePendingDropLayoutExists(cellPos.screenId);
}
switch (requestCode) {
case REQUEST_CREATE_SHORTCUT:
completeAddShortcut(intent, info.container, screenId,
cellPos.cellX, cellPos.cellY, info);
break;
case REQUEST_CREATE_APPWIDGET:
completeAddAppWidget(appWidgetId, info, null, null, false, true, null);
break;
case REQUEST_RECONFIGURE_APPWIDGET:
getStatsLogManager().logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
break;
case REQUEST_BIND_PENDING_APPWIDGET: {
int widgetId = appWidgetId;
LauncherAppWidgetInfo widgetInfo =
completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
if (widgetInfo != null) {
// Since the view was just bound, also launch the configure activity if needed
LauncherAppWidgetProviderInfo provider = mAppWidgetManager
.getLauncherAppWidgetInfo(widgetId, info.getTargetComponent());
if (provider != null) {
new WidgetAddFlowHandler(provider)
.startConfigActivity(this, widgetInfo,
REQUEST_RECONFIGURE_APPWIDGET);
}
}
break;
}
}
return screenId;
}
/**
* Process any pending activity result if it was put on hold for any reason like item binding.
*/
public void processActivityResult() {
if (mPendingActivityResult != null) {
handleActivityResult(mPendingActivityResult.requestCode,
mPendingActivityResult.resultCode, mPendingActivityResult.data);
mPendingActivityResult = null;
}
}
private void handleActivityResult(
final int requestCode, final int resultCode, final Intent data) {
if (isWorkspaceLoading()) {
// process the result once the workspace has loaded.
mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
return;
}
mPendingActivityResult = null;
if (requestCode == REQUEST_HOME_ROLE) {
if (resultCode != RESULT_OK) {
Toast.makeText(
this,
this.getString(R.string.set_default_home_app,
this.getString(R.string.derived_app_name)),
Toast.LENGTH_LONG).show();
}
return;
}
// Reset the startActivity waiting flag
final PendingRequestArgs requestArgs = mPendingRequestArgs;
setWaitingForResult(null);
if (requestArgs == null) {
return;
}
final int pendingAddWidgetId = requestArgs.getWidgetId();
Runnable exitSpringLoaded = MULTI_SELECT_EDIT_MODE.get() ? null
: () -> mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
if (requestCode == REQUEST_BIND_APPWIDGET) {
// This is called only if the user did not previously have permissions to bind widgets
final int appWidgetId = data != null ?
data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
if (resultCode == RESULT_CANCELED) {
completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_OK) {
addAppWidgetImpl(
appWidgetId, requestArgs, null,
requestArgs.getWidgetHandler(),
ON_ACTIVITY_RESULT_ANIMATION_DELAY);
}
return;
}
boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
requestCode == REQUEST_CREATE_APPWIDGET);
// We have special handling for widgets
if (isWidgetDrop) {
final int appWidgetId;
int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
: -1;
if (widgetId < 0) {
appWidgetId = pendingAddWidgetId;
} else {
appWidgetId = widgetId;
}
final int result;
if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
"returned from the widget configuration activity.");
result = RESULT_CANCELED;
completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
() -> getStateManager().goToState(NORMAL));
} else {
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(requestArgs);
if (requestArgs.container == CONTAINER_DESKTOP) {
// When the screen id represents an actual screen (as opposed to a rank)
// we make sure that the drop page actually exists.
int newScreenId = ensurePendingDropLayoutExists(presenterPos.screenId);
requestArgs.screenId = getCellPosMapper().mapPresenterToModel(
presenterPos.cellX, presenterPos.cellY, newScreenId, CONTAINER_DESKTOP)
.screenId;
}
final CellLayout dropLayout =
mWorkspace.getScreenWithId(presenterPos.screenId);
dropLayout.setDropPending(true);
final Runnable onComplete = new Runnable() {
@Override
public void run() {
completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
dropLayout.setDropPending(false);
}
};
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
}
return;
}
if (requestCode == REQUEST_RECONFIGURE_APPWIDGET
|| requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
if (resultCode == RESULT_OK) {
// Update the widget view.
completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
}
// Leave the widget in the pending state if the user canceled the configure.
return;
}
if (requestCode == REQUEST_CREATE_SHORTCUT) {
// Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
completeAdd(requestCode, data, -1, requestArgs);
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
} else if (resultCode == RESULT_CANCELED) {
mWorkspace.removeExtraEmptyScreenDelayed(
ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
}
}
mDragLayer.clearAnimatedView();
}
@Override
public void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
mPendingActivityRequestCode = -1;
handleActivityResult(requestCode, resultCode, data);
}
/**
* Check to see if a given screen id exists. If not, create it at the end, return the new id.
*
* @param screenId the screen id to check
* @return the new screen, or screenId if it exists
*/
private int ensurePendingDropLayoutExists(int screenId) {
CellLayout dropLayout = mWorkspace.getScreenWithId(screenId);
if (dropLayout == null) {
// it's possible that the add screen was removed because it was
// empty and a re-bind occurred
mWorkspace.addExtraEmptyScreens();
IntSet emptyPagesAdded = mWorkspace.commitExtraEmptyScreens();
return emptyPagesAdded.isEmpty() ? -1 : emptyPagesAdded.getArray().get(0);
}
return screenId;
}
@Thunk
void completeTwoStageWidgetDrop(
final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
CellLayout cellLayout = mWorkspace.getScreenWithId(
getCellPosMapper().mapModelToPresenter(requestArgs).screenId);
Runnable onCompleteRunnable = null;
int animationType = 0;
AppWidgetHostView boundWidget = null;
if (resultCode == RESULT_OK) {
animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
// Now that we are exiting the config activity with RESULT_OK.
// If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we can retrieve the
// PendingAppWidgetHostView from LauncherWidgetHolder (it was added to
// LauncherWidgetHolder when starting the config activity).
final AppWidgetHostView layout = enableAddAppWidgetViaConfigActivityV2()
? getWorkspace().getWidgetForAppWidgetId(appWidgetId)
: mAppWidgetHolder.createView(appWidgetId,
requestArgs.getWidgetHandler().getProviderInfo(this));
boundWidget = layout;
onCompleteRunnable = () -> {
completeAddAppWidget(appWidgetId, requestArgs, layout, null, false, true, null);
if (!isInState(EDIT_MODE)) {
mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
}
};
} else if (resultCode == RESULT_CANCELED) {
mAppWidgetHolder.deleteAppWidgetId(appWidgetId);
animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
}
if (mDragLayer.getAnimatedView() != null) {
mWorkspace.animateWidgetDrop(requestArgs, cellLayout,
(DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
animationType, boundWidget, true);
} else if (onCompleteRunnable != null) {
// The animated view may be null in the case of a rotation during widget configuration
onCompleteRunnable.run();
}
}
@Override
protected void onStop() {
super.onStop();
if (mDeferOverlayCallbacks) {
checkIfOverlayStillDeferred();
} else {
mOverlayManager.onActivityStopped();
}
hideKeyboard();
logStopAndResume(false /* isResume */);
mAppWidgetHolder.setActivityStarted(false);
NotificationListener.removeNotificationsChangedListener(getPopupDataProvider());
FloatingIconView.resetIconLoadResult();
AccessibilityManagerCompat.sendTestProtocolEventToTest(
this, LAUNCHER_ACTIVITY_STOPPED_MESSAGE);
}
@Override
protected void onStart() {
TraceHelper.INSTANCE.beginSection(ON_START_EVT);
super.onStart();
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityStarted();
}
mAppWidgetHolder.setActivityStarted(true);
TraceHelper.INSTANCE.endSection();
}
@Override
@CallSuper
protected void onDeferredResumed() {
logStopAndResume(true /* isResume */);
// Process any items that were added while Launcher was away.
ItemInstallQueue.INSTANCE.get(this)
.resumeModelPush(FLAG_ACTIVITY_PAUSED);
// Refresh shortcuts if the permission changed.
mModel.validateModelDataOnResume();
// Set the notification listener and fetch updated notifications when we resume
NotificationListener.addNotificationsChangedListener(mPopupDataProvider);
DiscoveryBounce.showForHomeIfNeeded(this);
mAppWidgetHolder.setActivityResumed(true);
// Listen for IME changes to keep state up to date.
getRootView().setWindowInsetsAnimationCallback(
new WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
@Override
public WindowInsets onProgress(WindowInsets windowInsets,
List<WindowInsetsAnimation> windowInsetsAnimations) {
return windowInsets;
}
@Override
public void onEnd(WindowInsetsAnimation animation) {
WindowInsets insets = getRootView().getRootWindowInsets();
boolean isImeVisible =
insets != null && insets.isVisible(WindowInsets.Type.ime());
getStatsLogManager().keyboardStateManager().setKeyboardState(
isImeVisible ? SHOW : HIDE);
}
});
}
private void logStopAndResume(boolean isResume) {
if (mModelCallbacks.getPendingExecutor() != null) return;
int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
int statsLogOrdinal = mStateManager.getState().statsLogOrdinal;
StatsLogManager.EventEnum event;
StatsLogManager.StatsLogger logger = getStatsLogManager().logger();
if (isResume) {
logger.withSrcState(LAUNCHER_STATE_BACKGROUND)
.withDstState(mStateManager.getState().statsLogOrdinal);
event = LAUNCHER_ONRESUME;
} else { /* command == Action.Command.STOP */
logger.withSrcState(mStateManager.getState().statsLogOrdinal)
.withDstState(LAUNCHER_STATE_BACKGROUND);
event = LAUNCHER_ONSTOP;
}
if (statsLogOrdinal == LAUNCHER_STATE_HOME && mWorkspace != null) {
logger.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
.setWorkspace(
LauncherAtom.WorkspaceContainer.newBuilder()
.setPageIndex(pageIndex)).build());
}
logger.log(event);
}
private void scheduleDeferredCheck() {
mHandler.removeCallbacks(mDeferredOverlayCallbacks);
postAsyncCallback(mHandler, mDeferredOverlayCallbacks);
}
private void checkIfOverlayStillDeferred() {
if (!mDeferOverlayCallbacks) {
return;
}
if (isStarted() && (!hasBeenResumed()
|| mStateManager.getState().hasFlag(FLAG_NON_INTERACTIVE))) {
return;
}
mDeferOverlayCallbacks = false;
// Move the client to the correct state. Calling the same method twice is no-op.
if (isStarted()) {
mOverlayManager.onActivityStarted();
}
if (hasBeenResumed()) {
mOverlayManager.onActivityResumed();
} else {
mOverlayManager.onActivityPaused();
}
if (!isStarted()) {
mOverlayManager.onActivityStopped();
}
}
public void deferOverlayCallbacksUntilNextResumeOrStop() {
mDeferOverlayCallbacks = true;
}
@Override
public void onStateSetStart(LauncherState state) {
super.onStateSetStart(state);
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
}
addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
if (state == SPRING_LOADED || state == EDIT_MODE) {
// Prevent any Un/InstallShortcutReceivers from updating the db while we are
// not on homescreen
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
mWorkspace.showPageIndicatorAtCurrentScroll();
mWorkspace.setClipChildren(false);
}
// When multiple pages are visible, show persistent page indicator
mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
mPrevLauncherState = mStateManager.getCurrentStableState();
if (mPrevLauncherState != state && ALL_APPS.equals(state)
// Making sure mAllAppsSessionLogId is null to avoid double logging.
&& mAllAppsSessionLogId == null) {
// creates new instance ID since new all apps session is started.
mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
if (getAllAppsEntryEvent().isPresent()) {
getStatsLogManager().logger()
.withContainerInfo(ContainerInfo.newBuilder()
.setWorkspace(WorkspaceContainer.newBuilder()
.setPageIndex(getWorkspace().getCurrentPage())).build())
.log(getAllAppsEntryEvent().get());
}
}
updateDisallowBack();
}
/**
* Returns {@link EventEnum} that should be logged when Launcher enters into AllApps state.
*/
protected Optional<EventEnum> getAllAppsEntryEvent() {
return Optional.of(LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH);
}
@Override
public void onStateSetEnd(LauncherState state) {
super.onStateSetEnd(state);
getAppWidgetHolder().setStateIsNormal(state == LauncherState.NORMAL);
getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
finishAutoCancelActionMode();
removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
// dispatch window state changed
getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
AccessibilityManagerCompat.sendStateEventToTest(this, state.ordinal);
if (state == NORMAL) {
// Re-enable any Un/InstallShortcutReceiver and now process any queued items
ItemInstallQueue.INSTANCE.get(this)
.resumeModelPush(FLAG_DRAG_AND_DROP);
// Clear any rotation locks when going to normal state
getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
}
if (ALL_APPS.equals(mPrevLauncherState) && !ALL_APPS.equals(state)
// Making sure mAllAppsSessionLogId is not null to avoid double logging.
&& mAllAppsSessionLogId != null) {
getAppsView().reset(false);
getAllAppsExitEvent().ifPresent(getStatsLogManager().logger()::log);
mAllAppsSessionLogId = null;
}
// Set screen title for Talkback
setTitle(state.getTitle());
}
/**
* Returns {@link EventEnum} that should be logged when Launcher exists from AllApps state.
*/
protected Optional<EventEnum> getAllAppsExitEvent() {
return Optional.of(LAUNCHER_ALLAPPS_EXIT);
}
@Override
protected void onResume() {
TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT);
super.onResume();
if (mDeferOverlayCallbacks) {
scheduleDeferredCheck();
} else {
mOverlayManager.onActivityResumed();
}
DragView.removeAllViews(this);
TraceHelper.INSTANCE.endSection();
}
@Override
protected void onPause() {
// Ensure that items added to Launcher are queued until Launcher returns
ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
super.onPause();
mDragController.cancelDrag();
mLastTouchUpTime = -1;
mDropTargetBar.animateToVisibility(false);
if (!mDeferOverlayCallbacks) {
mOverlayManager.onActivityPaused();
}
mAppWidgetHolder.setActivityResumed(false);
}
/**
* Restores the previous state, if it exists.
*
* @param savedState The previous state.
*/
private void restoreState(Bundle savedState) {
if (savedState == null) {
return;
}
int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
LauncherState[] stateValues = LauncherState.values();
LauncherState state = stateValues[stateOrdinal];
NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
boolean forceRestore = lastInstance != null
&& ((lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0
|| savedState.getBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME));
if (forceRestore || !state.shouldDisableRestore()) {
mStateManager.goToState(state, false /* animated */);
}
PendingRequestArgs requestArgs = savedState.getParcelable(
RUNTIME_STATE_PENDING_REQUEST_ARGS);
if (requestArgs != null) {
setWaitingForResult(requestArgs);
}
mPendingActivityRequestCode = savedState.getInt(
RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode);
mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
SparseArray<Parcelable> widgetsState =
savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL);
if (widgetsState != null) {
WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState);
}
}
/**
* Finds all the views we need and configure them properly.
*/
protected void setupViews() {
mStartupLatencyLogger.logStart(LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION);
inflateRootView(R.layout.launcher);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_VIEW_INFLATION);
mDragLayer = findViewById(R.id.drag_layer);
mFocusHandler = mDragLayer.getFocusIndicatorHelper();
mWorkspace = mDragLayer.findViewById(R.id.workspace);
mWorkspace.initParentViews(mDragLayer);
mOverviewPanel = findViewById(R.id.overview_panel);
mHotseat = findViewById(R.id.hotseat);
mHotseat.setWorkspace(mWorkspace);
// Setup the drag layer
mDragLayer.setup(mDragController, mWorkspace);
mWorkspace.setup(mDragController);
// Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
// default state, otherwise we will update to the wrong offsets in RTL
mWorkspace.lockWallpaperToDefaultPage();
if (!enableSmartspaceRemovalToggle()) {
mWorkspace.bindAndInitFirstWorkspaceScreen();
}
mDragController.addDragListener(mWorkspace);
// Get the search/delete/uninstall bar
mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);
// Setup Apps
mAppsView = findViewById(R.id.apps_view);
mAppsView.setAllAppsTransitionController(mAllAppsController);
// Setup Scrim
mScrimView = findViewById(R.id.scrim_view);
// Setup the drag controller (drop targets have to be added in reverse order in priority)
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mScrimView, mAppsView);
mWorkspace.getPageIndicator().setShouldAutoHide(true);
mWorkspace.getPageIndicator().setPaintColor(Themes.getAttrBoolean(
this, R.attr.isWorkspaceDarkText) ? Color.BLACK : Color.WHITE);
}
/**
* Add a shortcut to the workspace or to a Folder.
*
* @param data The intent describing the shortcut.
*/
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
int cellY, PendingRequestArgs args) {
if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT) {
return;
}
int[] cellXY = mTmpAddItemCellCoordinates;
CellLayout layout = getCellLayout(container, screenId);
WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
this, PinRequestHelper.getPinItemRequest(data), 0);
if (info == null) {
Log.e(TAG, "Unable to parse a valid shortcut result");
return;
}
if (container < 0) {
// Adding a shortcut to the Workspace.
final View view = mItemInflater.inflateItem(info);
boolean foundCellSpan = false;
// First we check if we already know the exact location where we want to add this item.
if (cellX >= 0 && cellY >= 0) {
cellXY[0] = cellX;
cellXY[1] = cellY;
foundCellSpan = true;
DragObject dragObject = new DragObject(getApplicationContext());
dragObject.dragInfo = info;
// If appropriate, either create a folder or add to an existing folder
if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
true, dragObject)) {
return;
}
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
true)) {
return;
}
} else {
foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
}
if (!foundCellSpan) {
mWorkspace.onNoCellFound(layout, info, /* logInstanceId= */ null);
return;
}
getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
AnimatorSet anim = new AnimatorSet();
anim.addListener(forEndCallback(() ->
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
bindInflatedItems(Collections.singletonList(Pair.create(info, view)), anim);
} else {
// Adding a shortcut to a Folder.
FolderIcon folderIcon = findFolderIcon(container);
if (folderIcon != null) {
folderIcon.getFolder().addFolderContent(info, args.rank, false);
} else {
Log.e(TAG, "Could not find folder with id " + container + " to add shortcut.");
}
}
}
@Override
public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
return (FolderIcon) mWorkspace.getViewByItemId(folderIconId);
}
/**
* Add a widget to the workspace.
*
* @param appWidgetId The app widget id
*/
@Thunk
void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
@Nullable AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo,
boolean showPendingWidget, boolean updateWidgetSize,
@Nullable Bitmap widgetPreviewBitmap) {
if (appWidgetInfo == null) {
appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId,
itemInfo.getTargetComponent());
}
if (hostView == null && !showPendingWidget) {
// Perform actual inflation because we're live
hostView = mAppWidgetHolder.createView(appWidgetId, appWidgetInfo);
}
LauncherAppWidgetInfo launcherInfo;
launcherInfo =
new LauncherAppWidgetInfo(
appWidgetId, appWidgetInfo.provider, appWidgetInfo, hostView);
launcherInfo.spanX = itemInfo.spanX;
launcherInfo.spanY = itemInfo.spanY;
launcherInfo.minSpanX = itemInfo.minSpanX;
launcherInfo.minSpanY = itemInfo.minSpanY;
launcherInfo.user = appWidgetInfo.getProfile();
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(itemInfo);
if (showPendingWidget) {
launcherInfo.restoreStatus = LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
PendingAppWidgetHostView pendingAppWidgetHostView = new PendingAppWidgetHostView(
this, mAppWidgetHolder, launcherInfo, appWidgetInfo, widgetPreviewBitmap);
hostView = pendingAppWidgetHostView;
} else if (hostView instanceof PendingAppWidgetHostView) {
((PendingAppWidgetHostView) hostView).setPreviewBitmapAndUpdateBackground(null);
// User has selected a widget config and exited the config activity, we can trigger
// re-inflation of PendingAppWidgetHostView to replace it with
// LauncherAppWidgetHostView in workspace.
completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
// Show resize frame on the newly inflated LauncherAppWidgetHostView.
LauncherAppWidgetHostView reInflatedHostView =
getWorkspace().getWidgetForAppWidgetId(appWidgetId);
showWidgetResizeFrame(
reInflatedHostView,
(LauncherAppWidgetInfo) reInflatedHostView.getTag(),
presenterPos);
// We always update widget size after re-inflating PendingAppWidgetHostView
WidgetSizes.updateWidgetSizeRanges(
reInflatedHostView, this, itemInfo.spanX, itemInfo.spanY);
return;
}
if (updateWidgetSize) {
WidgetSizes.updateWidgetSizeRanges(hostView, this, itemInfo.spanX, itemInfo.spanY);
}
if (itemInfo instanceof PendingAddWidgetInfo) {
launcherInfo.sourceContainer = ((PendingAddWidgetInfo) itemInfo).sourceContainer;
} else if (itemInfo instanceof PendingRequestArgs) {
launcherInfo.sourceContainer =
((PendingRequestArgs) itemInfo).getWidgetSourceContainer();
}
getModelWriter().addItemToDatabase(launcherInfo,
itemInfo.container, presenterPos.screenId, presenterPos.cellX, presenterPos.cellY);
hostView.setVisibility(View.VISIBLE);
mItemInflater.prepareAppWidget(hostView, launcherInfo);
if (!enableAddAppWidgetViaConfigActivityV2() || hostView.getParent() == null) {
mWorkspace.addInScreen(hostView, launcherInfo);
}
// Show the widget resize frame.
if (hostView instanceof LauncherAppWidgetHostView) {
final LauncherAppWidgetHostView launcherHostView = (LauncherAppWidgetHostView) hostView;
showWidgetResizeFrame(launcherHostView, launcherInfo, presenterPos);
}
}
/** Show widget resize frame. */
private void showWidgetResizeFrame(
LauncherAppWidgetHostView launcherHostView,
LauncherAppWidgetInfo launcherInfo,
CellPos presenterPos) {
CellLayout cellLayout = getCellLayout(launcherInfo.container, presenterPos.screenId);
// We should wait until launcher is not animating to show resize frame so that
// {@link View#hasIdentityMatrix()} returns true (no scale effect) from CellLayout and
// Workspace (they are widget's parent view). Otherwise widget's
// {@link View#getLocationInWindow(int[])} will set skewed location, causing resize
// frame not showing at skewed location in
// {@link AppWidgetResizeFrame#snapToWidget(boolean)}.
if (mStateManager.getState() == NORMAL && !mStateManager.isInTransition()) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
} else {
mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
@Override
public void onStateTransitionComplete(LauncherState finalState) {
if ((mPrevLauncherState == SPRING_LOADED || mPrevLauncherState == EDIT_MODE)
&& finalState == NORMAL) {
AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
mStateManager.removeStateListener(this);
}
}
});
}
}
private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mOverlayManager.onAttachedToWindow();
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mOverlayManager.onDetachedFromWindow();
closeContextMenu();
}
@Override
public Object onRetainNonConfigurationInstance() {
NonConfigInstance instance = new NonConfigInstance();
instance.config = new Configuration(mOldConfig);
return instance;
}
@Override
protected void onNewIntent(Intent intent) {
if (Utilities.isRunningInTestHarness()) {
Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
}
TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
super.onNewIntent(intent);
boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
!= Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
// Check this condition before handling isActionMain, as this will get reset.
boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
&& AbstractFloatingView.getTopOpenView(this) == null;
boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this);
if (isActionMain) {
if (!internalStateHandled) {
// In all these cases, only animate if we're already on home
AbstractFloatingView.closeAllOpenViewsExcept(
this, isStarted(), AbstractFloatingView.TYPE_LISTENER);
if (!isInState(NORMAL)) {
// Only change state, if not already the same. This prevents cancelling any
// animations running as part of resume
mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange());
}
// Reset the apps view
if (!alreadyOnHome) {
mAppsView.reset(isStarted() /* animate */);
}
if (shouldMoveToDefaultScreen && !mWorkspace.isHandlingTouch()) {
mWorkspace.post(mWorkspace::moveToDefaultScreen);
}
}
handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
mOverlayManager.hideOverlay(isStarted());
handleGestureContract(intent);
} else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
showAllAppsFromIntent(alreadyOnHome);
} else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) {
toggleAllApps(alreadyOnHome, true);
} else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) {
showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
ActivityAllAppsContainerView.AdapterHolder.WORK);
}
TraceHelper.INSTANCE.endSection();
}
/** Handle animating away split placeholder view when user taps on home button */
protected void handleSplitAnimationGoingToHome(EventEnum splitDismissReason) {
// Overridden
}
/**
* Toggles Launcher All Apps.
* @param focusSearch Indicates whether to make All Apps keyboard ready for search.
*/
public void toggleAllApps(boolean focusSearch) {
toggleAllApps(/* alreadyOnHome= */ true, focusSearch);
}
private void toggleAllApps(boolean alreadyOnHome, boolean focusSearch) {
if (getStateManager().isInStableState(ALL_APPS)) {
getStateManager().goToState(NORMAL, alreadyOnHome);
} else {
if (mWorkspace.isOverlayShown()) {
mOverlayManager.hideOverlay(/* animate */true);
}
AbstractFloatingView.closeAllOpenViews(this);
getStateManager().goToState(ALL_APPS, true /* animated */,
new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (focusSearch
&& mAppsView.getSearchUiManager().getEditText() != null) {
mAppsView.getSearchUiManager().getEditText().requestFocus();
}
}
});
}
}
protected void showAllAppsFromIntent(boolean alreadyOnHome) {
showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
ActivityAllAppsContainerView.AdapterHolder.MAIN);
}
private void showAllAppsWithSelectedTabFromIntent(boolean alreadyOnHome, int tab) {
AbstractFloatingView.closeAllOpenViews(this);
getStateManager().goToState(ALL_APPS, alreadyOnHome);
if (mAppsView.isSearching()) {
mAppsView.getSearchUiManager().resetSearch();
}
if (mAppsView.getCurrentPage() != tab) {
mAppsView.switchToTab(tab);
}
}
/**
* Handles gesture nav contract
*/
protected void handleGestureContract(Intent intent) {
GestureNavContract gnc = GestureNavContract.fromIntent(intent);
if (gnc != null) {
AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
FloatingSurfaceView.show(this, gnc);
}
}
@Override
public void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
IntSet synchronouslyBoundPages = mModelCallbacks.getSynchronouslyBoundPages();
if (synchronouslyBoundPages != null) {
synchronouslyBoundPages.forEach(screenId -> {
int pageIndex = mWorkspace.getPageIndexForScreenId(screenId);
if (pageIndex != PagedView.INVALID_PAGE) {
mWorkspace.restoreInstanceStateForChild(pageIndex);
}
});
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
mWorkspace.getCurrentPageScreenIds().getArray().toArray());
outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
AbstractFloatingView widgets = AbstractFloatingView
.getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
if (widgets != null) {
SparseArray<Parcelable> widgetsState = new SparseArray<>();
widgets.saveHierarchyState(widgetsState);
outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState);
} else {
outState.remove(RUNTIME_STATE_WIDGET_PANEL);
}
// We close any open folders and shortcut containers that are not safe for rebind,
// and we need to make sure this state is reflected.
AbstractFloatingView.closeAllOpenViewsExcept(
this, isStarted() && !isForceInvisible(), TYPE_REBIND_SAFE);
finishAutoCancelActionMode();
if (mPendingRequestArgs != null) {
outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
}
outState.putInt(RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode);
if (mPendingActivityResult != null) {
outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
}
super.onSaveInstanceState(outState);
}
@Override
public void onDestroy() {
super.onDestroy();
ACTIVITY_TRACKER.onContextDestroyed(this);
SettingsCache.INSTANCE.get(this).unregister(TOUCHPAD_NATURAL_SCROLLING,
mNaturalScrollingChangedListener);
ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
mModel.removeCallbacks(this);
mRotationHelper.destroy();
mAppWidgetHolder.stopListening();
mAppWidgetHolder.destroy();
mWidgetPickerDataProvider.destroy();
TextKeyListener.getInstance().release();
mModelCallbacks.clearPendingBinds();
LauncherAppState.getIDP(this).removeOnChangeListener(this);
// if Launcher activity is recreated, {@link Window} including {@link ViewTreeObserver}
// could be preserved in {@link ActivityThread#scheduleRelaunchActivity(IBinder)} if the
// previous activity has not stopped, which could happen when wallpaper detects a color
// changes while launcher is still loading.
getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
mOverlayManager.onActivityDestroyed();
PillColorProvider.getInstance(mWorkspace.getContext()).unregisterObserver();
}
public LauncherAccessibilityDelegate getAccessibilityDelegate() {
return mAccessibilityDelegate;
}
public LauncherDragController getDragController() {
return mDragController;
}
@Override
public DropTargetHandler getDropTargetHandler() {
return new DropTargetHandler(this);
}
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
super.startActivityForResult(intent, requestCode, options);
}
@Override
public void startIntentSenderForResult(IntentSender intent, int requestCode,
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
if (requestCode != -1) {
mPendingActivityRequestCode = requestCode;
}
try {
super.startIntentSenderForResult(intent, requestCode,
fillInIntent, flagsMask, flagsValues, extraFlags, options);
} catch (Exception e) {
throw new ActivityNotFoundException();
}
}
void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
WidgetAddFlowHandler addFlowHandler) {
if (LOGD) {
Log.d(TAG, "Adding widget from drop");
}
addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
}
/**
* If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled, we always add widget
* host view to workspace, otherwise we only add widget to host view if config activity is
* not started.
*/
void addAppWidgetImpl(int appWidgetId, ItemInfo info,
AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
final boolean isActivityStarted = addFlowHandler.startConfigActivity(
this, appWidgetId, info, REQUEST_CREATE_APPWIDGET);
if (!enableAddAppWidgetViaConfigActivityV2() && isActivityStarted) {
return;
}
// If FLAG_ENABLE_ADD_APP_WIDGET_VIA_CONFIG_ACTIVITY_V2 is enabled and config activity is
// started, we should remove the dropped AppWidgetHostView from drag layer and extract the
// Bitmap that shows the preview. Then pass the Bitmap to completeAddAppWidget() to create
// a PendingWidgetHostView.
Bitmap widgetPreviewBitmap = null;
if (isActivityStarted) {
DragView dropView = getDragLayer().clearAnimatedView();
if (dropView != null && dropView.containsAppWidgetHostView()) {
// Extracting Bitmap from dropView instead of its content view produces the correct
// bitmap.
widgetPreviewBitmap = createHardwareBitmap(
dropView.getWidth(), dropView.getHeight(), dropView::draw);
}
}
// Exit spring loaded mode if necessary after adding the widget; unless config activity was
// started.
Runnable onComplete = MULTI_SELECT_EDIT_MODE.get() ? null : () -> mStateManager.goToState(
NORMAL, SPRING_LOADED_EXIT_DELAY);
completeAddAppWidget(appWidgetId, info, boundWidget,
addFlowHandler.getProviderInfo(this), addFlowHandler.needsConfigure(),
false, widgetPreviewBitmap);
// Remove extra screen if widget drop concluded. If a config activity was started, extra
// screen will be removed when we get back its result.
if (!isActivityStarted) {
mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
}
}
public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
int[] cell, int spanX, int spanY) {
if (cell == null) {
CellPos modelPos = getCellPosMapper().mapPresenterToModel(0, 0, screenId, container);
info.screenId = modelPos.screenId;
} else {
CellPos modelPos = getCellPosMapper().mapPresenterToModel(
cell[0], cell[1], screenId, container);
info.screenId = modelPos.screenId;
info.cellX = modelPos.cellX;
info.cellY = modelPos.cellY;
}
info.container = container;
info.spanX = spanX;
info.spanY = spanY;
if (info instanceof PendingAddWidgetInfo) {
addAppWidgetFromDrop((PendingAddWidgetInfo) info);
} else { // info can only be PendingAddShortcutInfo
processShortcutFromDrop((PendingAddShortcutInfo) info);
}
}
/**
* Process a shortcut drop.
*/
private void processShortcutFromDrop(PendingAddShortcutInfo info) {
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: processShortcutFromDrop");
if (!info.getActivityInfo(this).startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
}
}
/**
* Process a widget drop.
*/
private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
AppWidgetHostView hostView = info.boundWidget;
final int appWidgetId;
WidgetAddFlowHandler addFlowHandler = info.getHandler();
if (hostView != null) {
// In the case where we've prebound the widget, we remove it from the DragLayer
if (LOGD) {
Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null");
}
getDragLayer().removeView(hostView);
appWidgetId = hostView.getAppWidgetId();
addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
// Clear the boundWidget so that it doesn't get destroyed.
info.boundWidget = null;
} else {
// In this case, we either need to start an activity to get permission to bind
// the widget, or we need to start an activity to configure the widget, or both.
if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
appWidgetId = CustomWidgetManager.INSTANCE.get(this)
.allocateCustomAppWidgetId(info.componentName);
} else {
appWidgetId = getAppWidgetHolder().allocateAppWidgetId();
}
Bundle options = info.bindOptions;
boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
appWidgetId, info.info, options);
if (success) {
addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
} else {
addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
}
}
}
/**
* Creates and adds new folder to CellLayout
*/
public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
int cellY) {
final FolderInfo folderInfo = new FolderInfo();
// Update the model
getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
// Create the view
FolderIcon newFolder = (FolderIcon) mItemInflater.inflateItem(folderInfo, layout);
mWorkspace.addInScreen(newFolder, folderInfo);
// Force measure the new folder icon
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
parent.getShortcutsAndWidgets().measureChild(newFolder);
return newFolder;
}
@Override
public Rect getFolderBoundingBox() {
// We need to bound the folder to the currently visible workspace area
return getWorkspace().getPageAreaRelativeToDragLayer();
}
@Override
public void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
int left = inOutPosition[0];
int top = inOutPosition[1];
DeviceProfile grid = getDeviceProfile();
int distFromEdgeOfScreen = getWorkspace().getPaddingLeft();
if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
// Center the folder if it is very close to being centered anyway, by virtue of
// filling the majority of the viewport. ie. remove it from the uncanny valley
// of centeredness.
left = (grid.availableWidthPx - width) / 2;
} else if (width >= bounds.width()) {
// If the folder doesn't fit within the bounds, center it about the desired bounds
left = bounds.left + (bounds.width() - width) / 2;
}
if (height >= bounds.height()) {
// Folder height is greater than page height, center on page
top = bounds.top + (bounds.height() - height) / 2;
} else {
// Folder height is less than page height, so bound it to the absolute open folder
// bounds if necessary
Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
}
inOutPosition[0] = left;
inOutPosition[1] = top;
}
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
* @param v the view being removed.
* @param itemInfo the {@link ItemInfo} for this view.
* @param deleteFromDb whether or not to delete this item from the db.
*/
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
return removeItem(v, itemInfo, deleteFromDb, null);
}
/**
* Unbinds the view for the specified item, and removes the item and all its children.
*
* @param v the view being removed.
* @param itemInfo the {@link ItemInfo} for this view.
* @param deleteFromDb whether or not to delete this item from the db.
* @param reason the resaon for removal.
*/
public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
@Nullable final String reason) {
if (itemInfo instanceof WorkspaceItemInfo) {
View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container);
if (collectionIcon instanceof FolderIcon folderIcon) {
// Remove the shortcut from the folder before removing it from launcher
Folder folder = folderIcon.getFolder();
folder.removeFolderContent(true, itemInfo);
} else if (collectionIcon instanceof AppPairIcon appPairIcon) {
removeItem(appPairIcon, appPairIcon.getInfo(), deleteFromDb,
"removing app pair because one of its member apps was removed");
} else {
mWorkspace.removeWorkspaceItem(v);
}
if (deleteFromDb) {
getModelWriter().deleteItemFromDatabase(itemInfo, reason);
}
} else if (itemInfo instanceof CollectionInfo ci) {
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
getModelWriter().deleteCollectionAndContentsFromDatabase(ci);
}
} else if (itemInfo instanceof LauncherAppWidgetInfo) {
final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
mWorkspace.removeWorkspaceItem(v);
if (deleteFromDb) {
getModelWriter().deleteWidgetInfo(widgetInfo, getAppWidgetHolder(), reason);
}
} else {
return false;
}
return true;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
TestLogging.recordKeyEvent(TestProtocol.SEQUENCE_MAIN, "Key event", event);
return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mTouchInProgress = true;
break;
case MotionEvent.ACTION_UP:
mLastTouchUpTime = SystemClock.uptimeMillis();
// Follow through
case MotionEvent.ACTION_CANCEL:
mTouchInProgress = false;
break;
}
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
return super.dispatchTouchEvent(ev);
}
@Override
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public void onBackPressed() {
getOnBackAnimationCallback().onBackInvoked();
}
protected void onBackStarted() {
mStateManager.getState().onBackStarted(this);
}
protected void onStateBack() {
mStateManager.getState().onBackInvoked(this);
}
protected void onBackCancelled() {
mStateManager.getState().onBackCancelled(this);
}
protected void onScreenOnChanged(boolean isOn) {
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
if (!isOn && mPendingRequestArgs == null) {
if (!isInState(NORMAL)) {
onUiChangedWhileSleeping();
}
mStateManager.goToState(NORMAL);
}
}
@Override
public RunnableList startActivitySafely(View v, Intent intent, ItemInfo item) {
if (!hasBeenResumed()) {
RunnableList result = new RunnableList();
// Workaround an issue where the WM launch animation is clobbered when finishing the
// recents animation into launcher. Defer launching the activity until Launcher is
// next resumed.
addEventCallback(EVENT_RESUMED, () -> {
RunnableList actualResult = startActivitySafely(v, intent, item);
if (actualResult != null) {
actualResult.add(result::executeAllAndDestroy);
} else {
result.executeAllAndDestroy();
}
});
if (mOnDeferredActivityLaunchCallback != null) {
mOnDeferredActivityLaunchCallback.run();
mOnDeferredActivityLaunchCallback = null;
}
return result;
}
RunnableList result = super.startActivitySafely(v, intent, item);
if (result != null && v instanceof BubbleTextView) {
// This is set to the view that launched the activity that navigated the user away
// from launcher. Since there is no callback for when the activity has finished
// launching, enable the press state and keep this reference to reset the press
// state when we return to launcher.
BubbleTextView btv = (BubbleTextView) v;
btv.setStayPressed(true);
result.add(() -> btv.setStayPressed(false));
}
return result;
}
boolean isHotseatLayout(View layout) {
// TODO: Remove this method
return mHotseat != null && (layout == mHotseat);
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
// The widget preview db can result in holding onto over
// 3MB of memory for caching which isn't necessary.
SQLiteDatabase.releaseMemory();
// This clears all widget bitmaps from the widget tray
// TODO(hyunyoungs)
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
final boolean result = super.dispatchPopulateAccessibilityEvent(event);
final List<CharSequence> text = event.getText();
text.clear();
// Populate event with a fake title based on the current state.
// TODO: When can workspace be null?
text.add(mWorkspace == null
? getString(R.string.home_screen)
: mStateManager.getState().getDescription(this));
return result;
}
@Override
public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
return mModelCallbacks.getPagesToBindSynchronously(orderedScreenIds);
}
@Override
public void startBinding() {
mModelCallbacks.startBinding();
}
@Override
public void setIsFirstPagePinnedItemEnabled(boolean isFirstPagePinnedItemEnabled) {
mModelCallbacks.setIsFirstPagePinnedItemEnabled(isFirstPagePinnedItemEnabled);
}
@Override
public void bindScreens(IntArray orderedScreenIds) {
mModelCallbacks.bindScreens(orderedScreenIds);
}
/**
* Remove odd number because they are already included when isTwoPanels and add the pair screen
* if not present.
*/
private IntArray filterTwoPanelScreenIds(IntArray orderedScreenIds) {
IntSet screenIds = IntSet.wrap(orderedScreenIds);
orderedScreenIds.forEach(screenId -> {
if (screenId % 2 == 1) {
screenIds.remove(screenId);
// In case the pair is not added, add it
if (!mWorkspace.containsScreenId(screenId - 1)) {
screenIds.add(screenId - 1);
}
}
});
return screenIds.getArray();
}
@Override
public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
ArrayList<ItemInfo> addAnimated) {
mModelCallbacks.bindAppsAdded(newScreens, addNotAnimated, addAnimated);
}
/**
* Bind the items start-end from the list.
*
* Implementation of the method from LauncherModel.Callbacks.
*/
@Override
public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
bindInflatedItems(items.stream()
.map(i -> Pair.create(i, getItemInflater().inflateItem(i)))
.collect(Collectors.toList()),
forceAnimateIcons ? new AnimatorSet() : null);
}
@Override
public void bindInflatedItems(List<Pair<ItemInfo, View>> items) {
bindInflatedItems(items, null);
}
/**
* Bind all the items in the map, ignoring any null views
*
* @param boundAnim if non-null, uses it to create and play the bounce animation for added views
*/
public void bindInflatedItems(
List<Pair<ItemInfo, View>> shortcuts, @Nullable AnimatorSet boundAnim) {
// Get the list of added items and intersect them with the set of items here
Workspace<?> workspace = mWorkspace;
int newItemsScreenId = -1;
int index = 0;
for (Pair<ItemInfo, View> e : shortcuts) {
final ItemInfo item = e.first;
// Remove colliding items.
CellPos presenterPos = getCellPosMapper().mapModelToPresenter(item);
if (item.container == CONTAINER_DESKTOP) {
CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
View occupiedView = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
Object tag = occupiedView == null ? null : occupiedView.getTag();
String desc = "Collision while binding workspace item: " + item
+ ". Collides with " + tag;
if (FeatureFlags.IS_STUDIO_BUILD) {
throw (new RuntimeException(desc));
} else {
getModelWriter().deleteItemFromDatabase(item, desc);
continue;
}
}
}
View view = e.second;
if (view == null) {
continue;
}
if (enableWorkspaceInflation() && view instanceof LauncherAppWidgetHostView lv) {
view = getAppWidgetHolder().attachViewToHostAndGetAttachedView(lv);
}
workspace.addInScreenFromBind(view, item);
if (boundAnim != null) {
// Animate all the applications up now
view.setAlpha(0f);
view.setScaleX(0f);
view.setScaleY(0f);
boundAnim.play(createNewAppBounceAnimation(view, index++));
newItemsScreenId = presenterPos.screenId;
}
}
// Animate to the correct page
if (boundAnim != null && newItemsScreenId > -1) {
int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
final Runnable startBounceAnimRunnable = boundAnim::start;
if (canAnimatePageChange() && newItemsScreenId != currentScreenId) {
// We post the animation slightly delayed to prevent slowdowns
// when we are loading right after we return to launcher.
mWorkspace.postDelayed(() -> {
closeOpenViews(false);
mWorkspace.snapToPage(newScreenIndex);
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}, NEW_APPS_PAGE_MOVE_DELAY);
} else {
mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
}
}
workspace.requestLayout();
}
/**
* Add the views for a widget to the workspace.
*/
public void bindAppWidget(LauncherAppWidgetInfo item) {
View view = mItemInflater.inflateItem(item);
if (view != null) {
mWorkspace.addInScreen(view, item);
mWorkspace.requestLayout();
}
}
/**
* Restores a pending widget.
*
* @param appWidgetId The app widget id
*/
private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
if (!(view instanceof PendingAppWidgetHostView)) {
Log.e(TAG, "Widget update called, when the widget no longer exists.");
return null;
}
LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
info.restoreStatus = finalRestoreFlag;
if (info.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
info.pendingItemInfo = null;
}
PendingAppWidgetHostView pv = (PendingAppWidgetHostView) view;
if (pv.isReinflateIfNeeded()) {
pv.reInflate();
}
getModelWriter().updateItemInDatabase(info);
return info;
}
/**
* Call back when ModelCallbacks finish binding the Launcher data.
*/
@TargetApi(Build.VERSION_CODES.S)
public void bindComplete(int workspaceItemCount, boolean isBindSync) {
if (mOnInitialBindListener != null) {
getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
mOnInitialBindListener = null;
}
if (!isBindSync) {
mStartupLatencyLogger
.logCardinality(workspaceItemCount)
.logEnd(LauncherLatencyEvent.LAUNCHER_LATENCY_STARTUP_WORKSPACE_LOADER_ASYNC);
}
MAIN_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
mStartupLatencyLogger
.logEnd(LAUNCHER_LATENCY_STARTUP_TOTAL_DURATION)
.log()
.reset();
});
}
@Override
public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks,
RunnableList onCompleteSignal, int workspaceItemCount, boolean isBindSync) {
mModelCallbacks.onInitialBindComplete(boundPages, pendingTasks, onCompleteSignal,
workspaceItemCount, isBindSync);
if (mIsColdStartupAfterReboot) {
Trace.endAsyncSection(COLD_STARTUP_TRACE_METHOD_NAME,
COLD_STARTUP_TRACE_COOKIE);
}
}
/**
* Callback saying that there aren't any more items to bind.
* <p>
* Implementation of the method from LauncherModel.Callbacks.
*/
public void finishBindingItems(IntSet pagesBoundFirst) {
mModelCallbacks.finishBindingItems(pagesBoundFirst);
}
private boolean canAnimatePageChange() {
if (mDragController.isDragging()) {
return false;
} else {
return (SystemClock.uptimeMillis() - mLastTouchUpTime)
> (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
}
}
/**
* Finds the first view on homescreen matching the provided parameters, optimized to finding a
* suitable view for the app close animation.
*
* @param svi The StableViewInfo of the preferred item to match to if it exists or null
* @param packageName The package name of the app to match.
* @param user The user of the app to match.
*/
public @Nullable View getFirstHomeElementForAppClose(
@Nullable StableViewInfo svi, String packageName, UserHandle user) {
final Predicate<ItemInfo> preferredItem = svi == null ? i -> false : svi::matches;
final Predicate<ItemInfo> packageAndUserAndApp = info -> info != null
&& info.itemType == ITEM_TYPE_APPLICATION
&& info.user.equals(user)
&& TextUtils.equals(info.getTargetPackage(), packageName);
// Look for the item inside the folder at the current page
Folder folder = Folder.getOpen(this);
if (folder != null) {
View v = folder.getFirstMatch(preferredItem, packageAndUserAndApp);
if (v == null) {
folder.close(isStarted() && !isForceInvisible());
} else {
return v;
}
}
List<CellLayout> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
containers.add(mWorkspace.getHotseat());
mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
LauncherBindableItemsContainer visibleContainer =
op -> mapOverCellLayouts(containerArray, op);
// Order: Preferred item by itself or in folder, then by matching package/user
return visibleContainer.getFirstMatch(
preferredItem, forFolderMatch(preferredItem),
packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
}
private ValueAnimator createNewAppBounceAnimation(View v, int i) {
ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
.setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY);
bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
return bounceAnim;
}
/**
* Informs us that the overlay (-1 screen, typically), has either become visible or invisible.
*/
public void onOverlayVisibilityChanged(boolean visible) {
getStatsLogManager().logger()
.withSrcState(LAUNCHER_STATE_HOME)
.withDstState(LAUNCHER_STATE_HOME)
.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
.setWorkspace(WorkspaceContainer.newBuilder()
.setPageIndex(visible ? 0 : -1))
.build())
.log(visible ? LAUNCHER_SWIPELEFT : LAUNCHER_SWIPERIGHT);
}
/**
* Informs us that the page transition has ended, so that we can react to the newly selected
* page if we want to.
*/
public void onPageEndTransition() {}
/**
* See {@code LauncherBindingDelegate}
*/
@Override
@TargetApi(Build.VERSION_CODES.S)
@UiThread
public void bindAllApplications(AppInfo[] apps, int flags,
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
mModelCallbacks.bindAllApplications(apps, flags, packageUserKeytoUidMap);
Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
DISPLAY_ALL_APPS_TRACE_COOKIE);
}
/**
* See {@code LauncherBindingDelegate}
*/
@Override
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
mModelCallbacks.bindDeepShortcutMap(deepShortcutMapCopy);
}
@Override
public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
}
/**
* See {@code LauncherBindingDelegate}
*/
@Override
public void bindItemsUpdated(Set<ItemInfo> updates) {
mModelCallbacks.bindItemsUpdated(updates);
}
/**
* See {@code LauncherBindingDelegate}
*/
@Override
public void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) {
mModelCallbacks.bindWorkspaceComponentsRemoved(matcher);
}
@Override
public void bindItemsModified(List<ItemInfo> items) {
mModelCallbacks.bindItemsModified(items);
}
/**
* See {@code LauncherBindingDelegate}
*/
@Override
public void bindAllWidgets(@NonNull final List<WidgetsListBaseEntry> allWidgets) {
mModelCallbacks.bindAllWidgets(allWidgets);
}
@Override
public void bindSmartspaceWidget() {
mModelCallbacks.bindSmartspaceWidget();
}
@Override
public void bindStringCache(StringCache cache) {
mModelCallbacks.bindStringCache(cache);
}
/**
* @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
* refreshes the widgets and shortcuts associated with the given package/user
*/
public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
}
/**
* $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
*/
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
super.dump(prefix, fd, writer, args);
if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
writer.println(prefix + "Workspace Items");
for (int i = 0; i < mWorkspace.getPageCount(); i++) {
writer.println(prefix + " Homescreen " + i);
ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
for (int j = 0; j < layout.getChildCount(); j++) {
Object tag = layout.getChildAt(j).getTag();
if (tag != null) {
writer.println(prefix + " " + tag);
}
}
}
writer.println(prefix + " Hotseat");
mHotseat.dump(prefix, writer);
ViewGroup layout = mHotseat.getShortcutsAndWidgets();
for (int j = 0; j < layout.getChildCount(); j++) {
Object tag = layout.getChildAt(j).getTag();
if (tag != null) {
writer.println(prefix + " " + tag);
}
}
}
writer.println(prefix + "Misc:");
dumpMisc(prefix + "\t", writer);
writer.println(prefix + "\tmWorkspaceLoading=" + mModelCallbacks.getWorkspaceLoading());
writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs
+ " mPendingActivityResult=" + mPendingActivityResult);
writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
writer.println(prefix + "\tmAppWidgetHolder.isListening: "
+ mAppWidgetHolder.isListening());
// b/349929393
// The only way to reproduce this bug is to ensure that onLayout never gets called. This
// theoretically is impossible, so these logs are being added to test if that actually is
// what is happening.
writer.println(prefix + "\tmWorkspace.mHasOnLayoutBeenCalled="
+ mWorkspace.mHasOnLayoutBeenCalled);
for (int i = 0; i < mWorkspace.getPageCount(); i++) {
CellLayout cellLayout = (CellLayout) mWorkspace.getPageAt(i);
writer.println(prefix + "\tcellLayout." + i + ".mHasOnLayoutBeenCalled="
+ cellLayout.mHasOnLayoutBeenCalled);
writer.println(prefix + "\tshortcutAndWidgetContainer." + i + ".mHasOnLayoutBeenCalled="
+ cellLayout.getShortcutsAndWidgets().mHasOnLayoutBeenCalled);
}
// Extra logging for general debugging
mDragLayer.dump(prefix, writer);
mStateManager.dump(prefix, writer);
mPopupDataProvider.dump(prefix, writer);
mWidgetPickerDataProvider.dump(prefix, writer);
mDeviceProfile.dump(this, prefix, writer);
mAppsView.getAppsStore().dump(prefix, writer);
mAppsView.getPersonalAppList().dump(prefix, writer);
if (mAppsView.shouldShowTabs()) {
mAppsView.getWorkAppList().dump(prefix, writer);
}
try {
FileLog.flushAll(writer);
} catch (Exception e) {
// Ignore
}
mModel.dumpState(prefix, fd, writer, args);
mOverlayManager.dump(prefix, writer);
ACTIVITY_TRACKER.dump(prefix, writer);
MSDLPlayerWrapper.INSTANCE.get(getApplicationContext()).dump(prefix, writer);
}
/**
* Populates the list of shortcuts. Logic delegated to {@Link KeyboardShortcutsDelegate}.
*
* @param data The data list to populate with shortcuts.
* @param menu The current menu, which may be null.
* @param deviceId The id for the connected device the shortcuts should be provided for.
*/
@Override
public void onProvideKeyboardShortcuts(
List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
mKeyboardShortcutsDelegate.onProvideKeyboardShortcuts(data, menu, deviceId);
super.onProvideKeyboardShortcuts(data, menu, deviceId);
}
/**
* Logic delegated to {@Link KeyboardShortcutsDelegate}.
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
*/
@Override
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
Boolean result = mKeyboardShortcutsDelegate.onKeyShortcut(keyCode, event);
return result != null ? result : super.onKeyShortcut(keyCode, event);
}
/**
* Logic delegated to {@Link KeyboardShortcutsDelegate}.
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Boolean result = mKeyboardShortcutsDelegate.onKeyDown(keyCode, event);
return result != null ? result : super.onKeyDown(keyCode, event);
}
/**
* Logic delegated to {@Link KeyboardShortcutsDelegate}.
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
Boolean result = mKeyboardShortcutsDelegate.onKeyUp(keyCode, event);
return result != null ? result : super.onKeyUp(keyCode, event);
}
/**
* Shows the default options popup
*/
public void showDefaultOptions(float x, float y) {
OptionsPopupView.show(this, getPopupTarget(x, y), OptionsPopupView.getOptions(this),
false);
}
@Override
public boolean canUseMultipleShadesForPopup() {
return getTopOpenViewWithType(this, TYPE_FOLDER) == null
&& getStateManager().getState() != LauncherState.ALL_APPS;
}
@Override
public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
out.add(getAllAppsController());
out.add(getWorkspace());
}
public TouchController[] createTouchControllers() {
return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
}
public void onDragLayerHierarchyChanged() {
updateDisallowBack();
}
protected void addBackAnimationCallback(BackPressHandler callback) {
mBackPressedHandlers.add(callback);
}
protected void removeBackAnimationCallback(BackPressHandler callback) {
mBackPressedHandlers.remove(callback);
}
private void updateDisallowBack() {
if (BuildCompat.isAtLeastV()
&& Flags.enableDesktopWindowingMode()
&& !Flags.enableDesktopWindowingWallpaperActivity()
&& mDeviceProfile.isTablet) {
// TODO(b/333533253): Clean up after desktop wallpaper activity flag is rolled out
return;
}
LauncherRootView rv = getRootView();
if (rv != null) {
boolean isSplitSelectionEnabled = isSplitSelectionActive();
boolean disableBack = getStateManager().getState() == NORMAL
&& AbstractFloatingView.getTopOpenView(this) == null
&& !isSplitSelectionEnabled;
rv.setDisallowBackGesture(disableBack);
}
}
/** To be overridden by subclasses */
public boolean isSplitSelectionActive() {
// Overridden
return false;
}
/** Call to dismiss the intermediary split selection state. */
public void dismissSplitSelection(StatsLogManager.LauncherEvent splitDismissEvent) {
// Overridden; move this into ActivityContext if necessary for Taskbar
}
/**
* Callback for when launcher state transition completes after user swipes to home.
* @param finalState The final state of the transition.
*/
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
// Overridden
}
public void closeOpenViews() {
closeOpenViews(true);
}
protected void closeOpenViews(boolean animate) {
AbstractFloatingView.closeAllOpenViews(this, animate);
}
protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
return new LauncherAccessibilityDelegate(this);
}
/** Enables/disabled the hotseat prediction icon long press edu for testing. */
@VisibleForTesting
public void enableHotseatEdu(boolean enable) {}
/**
* Just a wrapper around the type cast to allow easier tracking of calls.
*/
public static <T extends Launcher> T cast(ActivityContext activityContext) {
return (T) activityContext;
}
public boolean supportsAdaptiveIconAnimation(View clickedView) {
return false;
}
/**
* Animates Launcher elements during a transition to the All Apps page.
*
* @param progress Transition progress from 0 to 1; where 0 => home and 1 => all apps.
*/
public void onAllAppsTransition(float progress) {
// No-Op
}
/**
* Animates Launcher elements during a transition to the Widgets pages.
*
* @param progress Transition progress from 0 to 1; where 0 => home and 1 => widgets.
*/
public void onWidgetsTransition(float progress) {
float scale = Utilities.mapToRange(progress, 0f, 1f, 1f,
mDeviceProfile.bottomSheetWorkspaceScale, EMPHASIZED);
WORKSPACE_WIDGET_SCALE.set(getWorkspace(), scale);
HOTSEAT_WIDGET_SCALE.set(getHotseat(), scale);
}
private static class NonConfigInstance {
public Configuration config;
}
/** Pauses view updates that should not be run during the app launch animation. */
public void pauseExpensiveViewUpdates() {
// Pause page indicator animations as they lead to layer trashing.
getWorkspace().getPageIndicator().pauseAnimations();
getWorkspace().mapOverItems((info, view) -> {
if (view instanceof LauncherAppWidgetHostView) {
((LauncherAppWidgetHostView) view).beginDeferringUpdates();
}
return false; // Return false to continue iterating through all the items.
});
}
/** Resumes view updates at the end of the app launch animation. */
public void resumeExpensiveViewUpdates() {
getWorkspace().getPageIndicator().skipAnimationsToEnd();
getWorkspace().mapOverItems((info, view) -> {
if (view instanceof LauncherAppWidgetHostView) {
((LauncherAppWidgetHostView) view).endDeferringUpdates();
}
return false; // Return false to continue iterating through all the items.
});
}
/**
* Returns {@code true} if there are visible tasks with windowing mode set to
* {@link android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
*/
public boolean areDesktopTasksVisible() {
return false; // Base launcher does not track desktop tasks
}
// Getters and Setters
public boolean isWorkspaceLocked() {
return isWorkspaceLoading() || mPendingRequestArgs != null;
}
public boolean isWorkspaceLoading() {
return mModelCallbacks.getWorkspaceLoading();
}
/**
* Returns true if a touch interaction is in progress
*/
public boolean isTouchInProgress() {
return mTouchInProgress;
}
public boolean isDraggingEnabled() {
// We prevent dragging when we are loading the workspace as it is possible to pick up a view
// that is subsequently removed from the workspace in startBinding().
return !isWorkspaceLoading();
}
public boolean isNaturalScrollingEnabled() {
return mIsNaturalScrollingEnabled;
}
public void setWaitingForResult(PendingRequestArgs args) {
mPendingRequestArgs = args;
}
/**
* Call this after onCreate to set or clear overlay.
*/
public void setLauncherOverlay(LauncherOverlayTouchProxy overlay) {
mWorkspace.setLauncherOverlay(overlay);
}
/**
* Persistent callback which notifies when an activity launch is deferred because the activity
* was not yet resumed.
*/
public void setOnDeferredActivityLaunchCallback(Runnable callback) {
mOnDeferredActivityLaunchCallback = callback;
}
/**
* Sets the next pages to bind synchronously on next bind.
* @param pages should not be null.
*/
public void setPagesToBindSynchronously(@NonNull IntSet pages) {
mModelCallbacks.setPagesToBindSynchronously(pages);
}
@Override
public CellPosMapper getCellPosMapper() {
return mCellPosMapper;
}
public RotationHelper getRotationHelper() {
return mRotationHelper;
}
public ViewGroupFocusHelper getFocusHandler() {
return mFocusHandler;
}
@Override
public StateManager<LauncherState, Launcher> getStateManager() {
return mStateManager;
}
@NonNull
@Override
public PopupDataProvider getPopupDataProvider() {
return mPopupDataProvider;
}
@NonNull
@Override
public WidgetPickerDataProvider getWidgetPickerDataProvider() {
return mWidgetPickerDataProvider;
}
@NonNull
public LauncherOverlayManager getOverlayManager() {
return mOverlayManager;
}
public AllAppsTransitionController getAllAppsController() {
return mAllAppsController;
}
@Override
public DragLayer getDragLayer() {
return mDragLayer;
}
@NonNull
@Override
public LauncherBindableItemsContainer getContent() {
return mWorkspace;
}
@Override
public ActivityAllAppsContainerView<Launcher> getAppsView() {
return mAppsView;
}
public Workspace<?> getWorkspace() {
return mWorkspace;
}
public Hotseat getHotseat() {
return mHotseat;
}
public <T extends View> T getOverviewPanel() {
return (T) mOverviewPanel;
}
public DropTargetBar getDropTargetBar() {
return mDropTargetBar;
}
@Override
public ScrimView getScrimView() {
return mScrimView;
}
public LauncherWidgetHolder getAppWidgetHolder() {
return mAppWidgetHolder;
}
public LauncherModel getModel() {
return mModel;
}
@Override
public ModelWriter getModelWriter() {
return mModelWriter;
}
public SharedPreferences getSharedPrefs() {
return mSharedPrefs;
}
public int getOrientation() {
return mOldConfig.orientation;
}
/**
* Returns the CellLayout of the specified container at the specified screen.
*
* @param screenId must be presenterPos and not modelPos.
*/
public CellLayout getCellLayout(int container, int screenId) {
return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
? mHotseat : mWorkspace.getScreenWithId(screenId);
}
@Override
public StringCache getStringCache() {
return mModelCallbacks.getStringCache();
}
/**
* Returns target rectangle for anchoring a popup menu.
*/
protected RectF getPopupTarget(float x, float y) {
float halfSize = getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
if (x < 0 || y < 0) {
x = mDragLayer.getWidth() / 2;
y = mDragLayer.getHeight() / 2;
}
return new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
}
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
/**
* @see LauncherState#getOverviewScaleAndOffset(Launcher)
*/
public float[] getNormalOverviewScaleAndOffset() {
return new float[] {NO_SCALE, NO_OFFSET};
}
/**
* Handles an app pair launch; overridden in
* {@link com.android.launcher3.uioverrides.QuickstepLauncher}
*/
public void launchAppPair(AppPairIcon appPairIcon) {
// Overridden
}
public boolean getIsFirstPagePinnedItemEnabled() {
return mModelCallbacks.getIsFirstPagePinnedItemEnabled();
}
/**
* Returns the animation coordinator for playing one-off animations
*/
public CannedAnimationCoordinator getAnimationCoordinator() {
return mAnimationCoordinator;
}
@Override
public View.OnLongClickListener getAllAppsItemLongClickListener() {
return ItemLongClickListener.INSTANCE_ALL_APPS;
}
@Override
public StatsLogManager getStatsLogManager() {
return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
}
@Override
@NonNull
public ItemInflater<Launcher> getItemInflater() {
return mItemInflater;
}
/**
* Returns the current popup for testing, if any.
*/
@VisibleForTesting
@Nullable
public ArrowPopup<?> getOptionsPopup() {
return findViewById(R.id.popup_container);
}
@Override
public OnClickListener getItemOnClickListener() {
return ItemClickHandler.INSTANCE;
}
// End of Getters and Setters
}