1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3;
18 
19 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
20 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
21 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
22 import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
23 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
24 
25 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
26 import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
27 import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
28 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
29 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
30 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
31 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
32 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
33 import static com.android.launcher3.LauncherState.ALL_APPS;
34 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
35 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
36 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
37 import static com.android.launcher3.LauncherState.NORMAL;
38 import static com.android.launcher3.LauncherState.NO_OFFSET;
39 import static com.android.launcher3.LauncherState.NO_SCALE;
40 import static com.android.launcher3.LauncherState.SPRING_LOADED;
41 import static com.android.launcher3.Utilities.postAsyncCallback;
42 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
43 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
44 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
45 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
46 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
47 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY;
48 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH;
49 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_EXIT;
50 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
51 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
52 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
53 import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
54 import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
55 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
56 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
57 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
58 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
59 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
60 import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
61 
62 import android.animation.Animator;
63 import android.animation.AnimatorListenerAdapter;
64 import android.animation.AnimatorSet;
65 import android.animation.ObjectAnimator;
66 import android.animation.ValueAnimator;
67 import android.annotation.TargetApi;
68 import android.app.Notification;
69 import android.app.NotificationChannel;
70 import android.app.NotificationManager;
71 import android.app.PendingIntent;
72 import android.appwidget.AppWidgetHostView;
73 import android.appwidget.AppWidgetManager;
74 import android.content.ActivityNotFoundException;
75 import android.content.BroadcastReceiver;
76 import android.content.ComponentCallbacks2;
77 import android.content.Context;
78 import android.content.Intent;
79 import android.content.IntentFilter;
80 import android.content.IntentSender;
81 import android.content.SharedPreferences;
82 import android.content.pm.PackageManager;
83 import android.content.res.Configuration;
84 import android.database.sqlite.SQLiteDatabase;
85 import android.graphics.Bitmap;
86 import android.graphics.Rect;
87 import android.graphics.RectF;
88 import android.os.Build;
89 import android.os.Bundle;
90 import android.os.CancellationSignal;
91 import android.os.Parcelable;
92 import android.os.Process;
93 import android.os.StrictMode;
94 import android.os.SystemClock;
95 import android.os.Trace;
96 import android.os.UserHandle;
97 import android.text.TextUtils;
98 import android.text.method.TextKeyListener;
99 import android.util.Log;
100 import android.util.SparseArray;
101 import android.view.KeyEvent;
102 import android.view.KeyboardShortcutGroup;
103 import android.view.KeyboardShortcutInfo;
104 import android.view.LayoutInflater;
105 import android.view.Menu;
106 import android.view.MotionEvent;
107 import android.view.View;
108 import android.view.ViewGroup;
109 import android.view.WindowManager.LayoutParams;
110 import android.view.accessibility.AccessibilityEvent;
111 import android.view.animation.OvershootInterpolator;
112 import android.widget.ImageView;
113 import android.widget.Toast;
114 
115 import androidx.annotation.CallSuper;
116 import androidx.annotation.NonNull;
117 import androidx.annotation.Nullable;
118 import androidx.annotation.StringRes;
119 import androidx.annotation.VisibleForTesting;
120 
121 import com.android.launcher3.DropTarget.DragObject;
122 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
123 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.LauncherAction;
124 import com.android.launcher3.allapps.AllAppsContainerView;
125 import com.android.launcher3.allapps.AllAppsStore;
126 import com.android.launcher3.allapps.AllAppsTransitionController;
127 import com.android.launcher3.allapps.DiscoveryBounce;
128 import com.android.launcher3.anim.AnimatorListeners;
129 import com.android.launcher3.anim.PropertyListBuilder;
130 import com.android.launcher3.compat.AccessibilityManagerCompat;
131 import com.android.launcher3.config.FeatureFlags;
132 import com.android.launcher3.dot.DotInfo;
133 import com.android.launcher3.dragndrop.DragController;
134 import com.android.launcher3.dragndrop.DragLayer;
135 import com.android.launcher3.dragndrop.DragOptions;
136 import com.android.launcher3.dragndrop.DragView;
137 import com.android.launcher3.dragndrop.LauncherDragController;
138 import com.android.launcher3.folder.FolderGridOrganizer;
139 import com.android.launcher3.folder.FolderIcon;
140 import com.android.launcher3.icons.BitmapRenderer;
141 import com.android.launcher3.icons.IconCache;
142 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
143 import com.android.launcher3.logger.LauncherAtom;
144 import com.android.launcher3.logging.FileLog;
145 import com.android.launcher3.logging.InstanceId;
146 import com.android.launcher3.logging.InstanceIdSequence;
147 import com.android.launcher3.logging.StatsLogManager;
148 import com.android.launcher3.model.BgDataModel.Callbacks;
149 import com.android.launcher3.model.ItemInstallQueue;
150 import com.android.launcher3.model.ModelUtils;
151 import com.android.launcher3.model.ModelWriter;
152 import com.android.launcher3.model.WidgetsModel;
153 import com.android.launcher3.model.data.AppInfo;
154 import com.android.launcher3.model.data.FolderInfo;
155 import com.android.launcher3.model.data.ItemInfo;
156 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
157 import com.android.launcher3.model.data.WorkspaceItemInfo;
158 import com.android.launcher3.notification.NotificationListener;
159 import com.android.launcher3.pm.PinRequestHelper;
160 import com.android.launcher3.pm.UserCache;
161 import com.android.launcher3.popup.ArrowPopup;
162 import com.android.launcher3.popup.PopupContainerWithArrow;
163 import com.android.launcher3.popup.PopupDataProvider;
164 import com.android.launcher3.popup.SystemShortcut;
165 import com.android.launcher3.qsb.QsbContainerView;
166 import com.android.launcher3.statemanager.StateManager;
167 import com.android.launcher3.statemanager.StateManager.StateHandler;
168 import com.android.launcher3.statemanager.StatefulActivity;
169 import com.android.launcher3.states.RotationHelper;
170 import com.android.launcher3.testing.TestLogging;
171 import com.android.launcher3.testing.TestProtocol;
172 import com.android.launcher3.touch.AllAppsSwipeController;
173 import com.android.launcher3.touch.ItemClickHandler;
174 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
175 import com.android.launcher3.util.ActivityResultInfo;
176 import com.android.launcher3.util.ActivityTracker;
177 import com.android.launcher3.util.ComponentKey;
178 import com.android.launcher3.util.IntArray;
179 import com.android.launcher3.util.IntSet;
180 import com.android.launcher3.util.ItemInfoMatcher;
181 import com.android.launcher3.util.MultiValueAlpha;
182 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
183 import com.android.launcher3.util.OnboardingPrefs;
184 import com.android.launcher3.util.PackageManagerHelper;
185 import com.android.launcher3.util.PackageUserKey;
186 import com.android.launcher3.util.PendingRequestArgs;
187 import com.android.launcher3.util.RunnableList;
188 import com.android.launcher3.util.SafeCloseable;
189 import com.android.launcher3.util.SystemUiController;
190 import com.android.launcher3.util.Themes;
191 import com.android.launcher3.util.Thunk;
192 import com.android.launcher3.util.TouchController;
193 import com.android.launcher3.util.TraceHelper;
194 import com.android.launcher3.util.UiThreadHelper;
195 import com.android.launcher3.util.ViewOnDrawExecutor;
196 import com.android.launcher3.views.ActivityContext;
197 import com.android.launcher3.views.FloatingSurfaceView;
198 import com.android.launcher3.views.OptionsPopupView;
199 import com.android.launcher3.views.ScrimView;
200 import com.android.launcher3.widget.LauncherAppWidgetHost;
201 import com.android.launcher3.widget.LauncherAppWidgetHostView;
202 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
203 import com.android.launcher3.widget.PendingAddShortcutInfo;
204 import com.android.launcher3.widget.PendingAddWidgetInfo;
205 import com.android.launcher3.widget.PendingAppWidgetHostView;
206 import com.android.launcher3.widget.WidgetAddFlowHandler;
207 import com.android.launcher3.widget.WidgetManagerHelper;
208 import com.android.launcher3.widget.custom.CustomWidgetManager;
209 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
210 import com.android.launcher3.widget.picker.WidgetsFullSheet;
211 import com.android.systemui.plugins.OverlayPlugin;
212 import com.android.systemui.plugins.PluginListener;
213 import com.android.systemui.plugins.shared.LauncherExterns;
214 import com.android.systemui.plugins.shared.LauncherOverlayManager;
215 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
216 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
217 
218 import java.io.FileDescriptor;
219 import java.io.PrintWriter;
220 import java.util.ArrayList;
221 import java.util.Collection;
222 import java.util.Collections;
223 import java.util.HashMap;
224 import java.util.HashSet;
225 import java.util.List;
226 import java.util.function.Predicate;
227 import java.util.function.Supplier;
228 import java.util.stream.Stream;
229 
230 /**
231  * Default launcher application.
232  */
233 public class Launcher extends StatefulActivity<LauncherState> implements LauncherExterns,
234         Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin>,
235         LauncherOverlayCallbacks {
236     public static final String TAG = "Launcher";
237 
238     public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
239 
240     static final boolean LOGD = false;
241 
242     static final boolean DEBUG_STRICT_MODE = false;
243 
244     private static final int REQUEST_CREATE_SHORTCUT = 1;
245     private static final int REQUEST_CREATE_APPWIDGET = 5;
246 
247     private static final int REQUEST_PICK_APPWIDGET = 9;
248 
249     private static final int REQUEST_BIND_APPWIDGET = 11;
250     public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
251     public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
252 
253     private static final int REQUEST_PERMISSION_CALL_PHONE = 14;
254 
255     private static final float BOUNCE_ANIMATION_TENSION = 1.3f;
256 
257     /**
258      * IntentStarter uses request codes starting with this. This must be greater than all activity
259      * request codes used internally.
260      */
261     protected static final int REQUEST_LAST = 100;
262 
263     // Type: int
264     private static final String RUNTIME_STATE = "launcher.state";
265     // Type: PendingRequestArgs
266     private static final String RUNTIME_STATE_PENDING_REQUEST_ARGS = "launcher.request_args";
267     // Type: int
268     private static final String RUNTIME_STATE_PENDING_REQUEST_CODE = "launcher.request_code";
269     // Type: ActivityResultInfo
270     private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
271     // Type: SparseArray<Parcelable>
272     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
273     // Type int[]
274     private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
275 
276     public static final String ON_CREATE_EVT = "Launcher.onCreate";
277     public static final String ON_START_EVT = "Launcher.onStart";
278     public static final String ON_RESUME_EVT = "Launcher.onResume";
279     public static final String ON_NEW_INTENT_EVT = "Launcher.onNewIntent";
280 
281     private StateManager<LauncherState> mStateManager;
282 
283     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
284 
285     // How long to wait before the new-shortcut animation automatically pans the workspace
286     @VisibleForTesting public static final int NEW_APPS_PAGE_MOVE_DELAY = 500;
287     private static final int NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS = 5;
288     @Thunk @VisibleForTesting public static final int NEW_APPS_ANIMATION_DELAY = 500;
289 
290     private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
291 
292     private static final String DISPLAY_WORKSPACE_TRACE_METHOD_NAME = "DisplayWorkspaceFirstFrame";
293     private static final String DISPLAY_ALL_APPS_TRACE_METHOD_NAME = "DisplayAllApps";
294     public static final int DISPLAY_WORKSPACE_TRACE_COOKIE = 0;
295     public static final int DISPLAY_ALL_APPS_TRACE_COOKIE = 1;
296 
297     private Configuration mOldConfig;
298 
299     @Thunk
300     Workspace mWorkspace;
301     @Thunk
302     DragLayer mDragLayer;
303     private DragController mDragController;
304 
305     private WidgetManagerHelper mAppWidgetManager;
306     private LauncherAppWidgetHost mAppWidgetHost;
307 
308     private final int[] mTmpAddItemCellCoordinates = new int[2];
309 
310     @Thunk
311     Hotseat mHotseat;
312 
313     private DropTargetBar mDropTargetBar;
314 
315     // Main container view for the all apps screen.
316     @Thunk
317     AllAppsContainerView mAppsView;
318     AllAppsTransitionController mAllAppsController;
319 
320     // Scrim view for the all apps and overview state.
321     @Thunk
322     ScrimView mScrimView;
323 
324     // UI and state for the overview panel
325     private View mOverviewPanel;
326 
327     @Thunk
328     boolean mWorkspaceLoading = true;
329 
330     // Used to notify when an activity launch has been deferred because launcher is not yet resumed
331     // TODO: See if we can remove this later
332     private Runnable mOnDeferredActivityLaunchCallback;
333 
334     private ViewOnDrawExecutor mPendingExecutor;
335 
336     private LauncherModel mModel;
337     private ModelWriter mModelWriter;
338     private IconCache mIconCache;
339     private LauncherAccessibilityDelegate mAccessibilityDelegate;
340 
341     private PopupDataProvider mPopupDataProvider;
342 
343     private IntSet mSynchronouslyBoundPages = new IntSet();
344     @NonNull private IntSet mPagesToBindSynchronously = new IntSet();
345 
346     // We only want to get the SharedPreferences once since it does an FS stat each time we get
347     // it from the context.
348     private SharedPreferences mSharedPrefs;
349     private OnboardingPrefs mOnboardingPrefs;
350 
351     // Activity result which needs to be processed after workspace has loaded.
352     private ActivityResultInfo mPendingActivityResult;
353     /**
354      * Holds extra information required to handle a result from an external call, like
355      * {@link #startActivityForResult(Intent, int)} or {@link #requestPermissions(String[], int)}
356      */
357     private PendingRequestArgs mPendingRequestArgs;
358     // Request id for any pending activity result
359     protected int mPendingActivityRequestCode = -1;
360 
361     private ViewGroupFocusHelper mFocusHandler;
362 
363     private RotationHelper mRotationHelper;
364 
365     protected LauncherOverlayManager mOverlayManager;
366     // If true, overlay callbacks are deferred
367     private boolean mDeferOverlayCallbacks;
368     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
369 
370     protected long mLastTouchUpTime = -1;
371     private boolean mTouchInProgress;
372 
373     private SafeCloseable mUserChangedCallbackCloseable;
374 
375     // New InstanceId is assigned to mAllAppsSessionLogId for each AllApps sessions.
376     // When Launcher is not in AllApps state mAllAppsSessionLogId will be null.
377     // User actions within AllApps state are logged with this InstanceId, to recreate AllApps
378     // session on the server side.
379     protected InstanceId mAllAppsSessionLogId;
380     private LauncherState mPrevLauncherState;
381 
382     @Override
383     @TargetApi(Build.VERSION_CODES.S)
onCreate(Bundle savedInstanceState)384     protected void onCreate(Bundle savedInstanceState) {
385         // Only use a hard-coded cookie since we only want to trace this once.
386         if (Utilities.ATLEAST_S) {
387             Trace.beginAsyncSection(
388                     DISPLAY_WORKSPACE_TRACE_METHOD_NAME, DISPLAY_WORKSPACE_TRACE_COOKIE);
389             Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
390                     DISPLAY_ALL_APPS_TRACE_COOKIE);
391         }
392         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT,
393                 TraceHelper.FLAG_UI_EVENT);
394         if (DEBUG_STRICT_MODE) {
395             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
396                     .detectDiskReads()
397                     .detectDiskWrites()
398                     .detectNetwork()   // or .detectAll() for all detectable problems
399                     .penaltyLog()
400                     .build());
401             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
402                     .detectLeakedSqlLiteObjects()
403                     .detectLeakedClosableObjects()
404                     .penaltyLog()
405                     .penaltyDeath()
406                     .build());
407         }
408 
409         if (Utilities.IS_DEBUG_DEVICE && FeatureFlags.NOTIFY_CRASHES.get()) {
410             final String notificationChannelId = "com.android.launcher3.Debug";
411             final String notificationChannelName = "Debug";
412             final String notificationTag = "Debug";
413             final int notificationId = 0;
414 
415             NotificationManager notificationManager = getSystemService(NotificationManager.class);
416             notificationManager.createNotificationChannel(new NotificationChannel(
417                     notificationChannelId, notificationChannelName,
418                     NotificationManager.IMPORTANCE_HIGH));
419 
420             Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
421                 String stackTrace = Log.getStackTraceString(throwable);
422 
423                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
424                 shareIntent.setType("text/plain");
425                 shareIntent.putExtra(Intent.EXTRA_TEXT, stackTrace);
426                 shareIntent = Intent.createChooser(shareIntent, null);
427                 PendingIntent sharePendingIntent = PendingIntent.getActivity(
428                         this, 0, shareIntent, PendingIntent.FLAG_UPDATE_CURRENT
429                 );
430 
431                 Notification notification = new Notification.Builder(this, notificationChannelId)
432                         .setSmallIcon(android.R.drawable.ic_menu_close_clear_cancel)
433                         .setContentTitle("Launcher crash detected!")
434                         .setStyle(new Notification.BigTextStyle().bigText(stackTrace))
435                         .addAction(android.R.drawable.ic_menu_share, "Share", sharePendingIntent)
436                         .build();
437                 notificationManager.notify(notificationTag, notificationId, notification);
438 
439                 Thread.UncaughtExceptionHandler defaultUncaughtExceptionHandler =
440                         Thread.getDefaultUncaughtExceptionHandler();
441                 if (defaultUncaughtExceptionHandler != null) {
442                     defaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
443                 }
444             });
445         }
446 
447         super.onCreate(savedInstanceState);
448 
449         LauncherAppState app = LauncherAppState.getInstance(this);
450         mOldConfig = new Configuration(getResources().getConfiguration());
451         mModel = app.getModel();
452 
453         mRotationHelper = new RotationHelper(this);
454         InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
455         initDeviceProfile(idp);
456         idp.addOnChangeListener(this);
457         mSharedPrefs = Utilities.getPrefs(this);
458         mIconCache = app.getIconCache();
459         mAccessibilityDelegate = createAccessibilityDelegate();
460 
461         mDragController = new LauncherDragController(this);
462         mAllAppsController = new AllAppsTransitionController(this);
463         mStateManager = new StateManager<>(this, NORMAL);
464 
465         mOnboardingPrefs = createOnboardingPrefs(mSharedPrefs);
466 
467         mAppWidgetManager = new WidgetManagerHelper(this);
468         mAppWidgetHost = createAppWidgetHost();
469         mAppWidgetHost.startListening();
470 
471         inflateRootView(R.layout.launcher);
472         setupViews();
473         crossFadeWithPreviousAppearance();
474         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
475 
476         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
477         if (internalStateHandled) {
478             if (savedInstanceState != null) {
479                 // InternalStateHandler has already set the appropriate state.
480                 // We dont need to do anything.
481                 savedInstanceState.remove(RUNTIME_STATE);
482             }
483         }
484         restoreState(savedInstanceState);
485         mStateManager.reapplyState();
486 
487         if (savedInstanceState != null) {
488             int[] pageIds = savedInstanceState.getIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS);
489             if (pageIds != null) {
490                 mPagesToBindSynchronously = IntSet.wrap(pageIds);
491             }
492         }
493 
494         if (!mModel.addCallbacksAndLoad(this)) {
495             if (!internalStateHandled) {
496                 // If we are not binding synchronously, show a fade in animation when
497                 // the first page bind completes.
498                 mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
499             }
500         }
501 
502         // For handling default keys
503         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
504 
505         setContentView(getRootView());
506         getRootView().dispatchInsets();
507 
508         // Listen for broadcasts
509         registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
510 
511         getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
512                 Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));
513 
514         if (mLauncherCallbacks != null) {
515             mLauncherCallbacks.onCreate(savedInstanceState);
516         }
517         mOverlayManager = getDefaultOverlay();
518         PluginManagerWrapper.INSTANCE.get(this).addPluginListener(this,
519                 OverlayPlugin.class, false /* allowedMultiple */);
520 
521         mRotationHelper.initialize();
522         TraceHelper.INSTANCE.endSection(traceToken);
523 
524         mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
525                 () -> getStateManager().goToState(NORMAL));
526 
527         if (Utilities.ATLEAST_R) {
528             getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
529         }
530     }
531 
getDefaultOverlay()532     protected LauncherOverlayManager getDefaultOverlay() {
533         return new LauncherOverlayManager() { };
534     }
535 
createOnboardingPrefs(SharedPreferences sharedPrefs)536     protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
537         return new OnboardingPrefs<>(this, sharedPrefs);
538     }
539 
getOnboardingPrefs()540     public OnboardingPrefs getOnboardingPrefs() {
541         return mOnboardingPrefs;
542     }
543 
544     @Override
onPluginConnected(OverlayPlugin overlayManager, Context context)545     public void onPluginConnected(OverlayPlugin overlayManager, Context context) {
546         switchOverlay(() -> overlayManager.createOverlayManager(this, this));
547     }
548 
549     @Override
onPluginDisconnected(OverlayPlugin plugin)550     public void onPluginDisconnected(OverlayPlugin plugin) {
551         switchOverlay(this::getDefaultOverlay);
552     }
553 
switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier)554     private void switchOverlay(Supplier<LauncherOverlayManager> overlaySupplier) {
555         if (mOverlayManager != null) {
556             mOverlayManager.onActivityDestroyed(this);
557         }
558         mOverlayManager = overlaySupplier.get();
559         if (getRootView().isAttachedToWindow()) {
560             mOverlayManager.onAttachedToWindow();
561         }
562         mDeferOverlayCallbacks = true;
563         checkIfOverlayStillDeferred();
564     }
565 
566     @Override
dispatchDeviceProfileChanged()567     protected void dispatchDeviceProfileChanged() {
568         super.dispatchDeviceProfileChanged();
569         mOverlayManager.onDeviceProvideChanged();
570     }
571 
572     @Override
onEnterAnimationComplete()573     public void onEnterAnimationComplete() {
574         super.onEnterAnimationComplete();
575         mRotationHelper.setCurrentTransitionRequest(REQUEST_NONE);
576         AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
577     }
578 
579     @Override
onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig)580     public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
581         super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
582         // Always update device profile when multi window mode changed.
583         initDeviceProfile(mDeviceProfile.inv);
584         dispatchDeviceProfileChanged();
585     }
586 
587     @Override
onConfigurationChanged(Configuration newConfig)588     public void onConfigurationChanged(Configuration newConfig) {
589         int diff = newConfig.diff(mOldConfig);
590         if ((diff & (CONFIG_ORIENTATION | CONFIG_SCREEN_SIZE)) != 0) {
591             onIdpChanged(false);
592         }
593 
594         mOldConfig.setTo(newConfig);
595         super.onConfigurationChanged(newConfig);
596     }
597 
598     @Override
onIdpChanged(boolean modelPropertiesChanged)599     public void onIdpChanged(boolean modelPropertiesChanged) {
600         initDeviceProfile(mDeviceProfile.inv);
601         dispatchDeviceProfileChanged();
602         reapplyUi();
603         mDragLayer.recreateControllers();
604 
605         // Calling onSaveInstanceState ensures that static cache used by listWidgets is
606         // initialized properly.
607         onSaveInstanceState(new Bundle());
608         mModel.rebindCallbacks();
609     }
610 
onAssistantVisibilityChanged(float visibility)611     public void onAssistantVisibilityChanged(float visibility) {
612         mHotseat.getQsb().setAlpha(1f - visibility);
613     }
614 
615     /**
616      * Called when one handed mode activated and deactivated.
617      * @param activated true if one handed mode activated, false otherwise.
618      */
onOneHandedStateChanged(boolean activated)619     public void onOneHandedStateChanged(boolean activated) {
620         mDragLayer.onOneHandedModeStateChanged(activated);
621     }
622 
initDeviceProfile(InvariantDeviceProfile idp)623     private void initDeviceProfile(InvariantDeviceProfile idp) {
624         // Load configuration-specific DeviceProfile
625         mDeviceProfile = idp.getDeviceProfile(this);
626         if (isInMultiWindowMode()) {
627             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(
628                     this, getMultiWindowDisplaySize());
629         }
630 
631         onDeviceProfileInitiated();
632         mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this);
633     }
634 
getRotationHelper()635     public RotationHelper getRotationHelper() {
636         return mRotationHelper;
637     }
638 
getFocusHandler()639     public ViewGroupFocusHelper getFocusHandler() {
640         return mFocusHandler;
641     }
642 
643     @Override
getStateManager()644     public StateManager<LauncherState> getStateManager() {
645         return mStateManager;
646     }
647 
648     private LauncherCallbacks mLauncherCallbacks;
649 
650     /**
651      * Call this after onCreate to set or clear overlay.
652      */
653     @Override
setLauncherOverlay(LauncherOverlay overlay)654     public void setLauncherOverlay(LauncherOverlay overlay) {
655         if (overlay != null) {
656             overlay.setOverlayCallbacks(this);
657         }
658         mWorkspace.setLauncherOverlay(overlay);
659     }
660 
661     @Override
runOnOverlayHidden(Runnable runnable)662     public void runOnOverlayHidden(Runnable runnable) {
663         getWorkspace().runOnOverlayHidden(runnable);
664     }
665 
setLauncherCallbacks(LauncherCallbacks callbacks)666     public boolean setLauncherCallbacks(LauncherCallbacks callbacks) {
667         mLauncherCallbacks = callbacks;
668         return true;
669     }
670 
isDraggingEnabled()671     public boolean isDraggingEnabled() {
672         // We prevent dragging when we are loading the workspace as it is possible to pick up a view
673         // that is subsequently removed from the workspace in startBinding().
674         return !isWorkspaceLoading();
675     }
676 
getPopupDataProvider()677     public PopupDataProvider getPopupDataProvider() {
678         return mPopupDataProvider;
679     }
680 
681     @Override
getDotInfoForItem(ItemInfo info)682     public DotInfo getDotInfoForItem(ItemInfo info) {
683         return mPopupDataProvider.getDotInfoForItem(info);
684     }
685 
686     @Override
invalidateParent(ItemInfo info)687     public void invalidateParent(ItemInfo info) {
688         if (info.container >= 0) {
689             View folderIcon = getWorkspace().getHomescreenIconByItemId(info.container);
690             if (folderIcon instanceof FolderIcon && folderIcon.getTag() instanceof FolderInfo) {
691                 if (new FolderGridOrganizer(getDeviceProfile().inv)
692                         .setFolderInfo((FolderInfo) folderIcon.getTag())
693                         .isItemInPreview(info.rank)) {
694                     folderIcon.invalidate();
695                 }
696             }
697         }
698     }
699 
700     /**
701      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
702      * a configuration step, this allows the proper animations to run after other transitions.
703      */
completeAdd( int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info)704     private int completeAdd(
705             int requestCode, Intent intent, int appWidgetId, PendingRequestArgs info) {
706         int screenId = info.screenId;
707         if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
708             // When the screen id represents an actual screen (as opposed to a rank) we make sure
709             // that the drop page actually exists.
710             screenId = ensurePendingDropLayoutExists(info.screenId);
711         }
712 
713         switch (requestCode) {
714             case REQUEST_CREATE_SHORTCUT:
715                 completeAddShortcut(intent, info.container, screenId, info.cellX, info.cellY, info);
716                 announceForAccessibility(R.string.item_added_to_workspace);
717                 break;
718             case REQUEST_CREATE_APPWIDGET:
719                 completeAddAppWidget(appWidgetId, info, null, null);
720                 break;
721             case REQUEST_RECONFIGURE_APPWIDGET:
722                 mStatsLogManager.logger().withItemInfo(info).log(LAUNCHER_WIDGET_RECONFIGURED);
723                 completeRestoreAppWidget(appWidgetId, LauncherAppWidgetInfo.RESTORE_COMPLETED);
724                 break;
725             case REQUEST_BIND_PENDING_APPWIDGET: {
726                 int widgetId = appWidgetId;
727                 LauncherAppWidgetInfo widgetInfo =
728                         completeRestoreAppWidget(widgetId, LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
729                 if (widgetInfo != null) {
730                     // Since the view was just bound, also launch the configure activity if needed
731                     LauncherAppWidgetProviderInfo provider = mAppWidgetManager
732                             .getLauncherAppWidgetInfo(widgetId);
733                     if (provider != null) {
734                         new WidgetAddFlowHandler(provider)
735                                 .startConfigActivity(this, widgetInfo,
736                                         REQUEST_RECONFIGURE_APPWIDGET);
737                     }
738                 }
739                 break;
740             }
741         }
742         return screenId;
743     }
744 
handleActivityResult( final int requestCode, final int resultCode, final Intent data)745     private void handleActivityResult(
746             final int requestCode, final int resultCode, final Intent data) {
747         if (isWorkspaceLoading()) {
748             // process the result once the workspace has loaded.
749             mPendingActivityResult = new ActivityResultInfo(requestCode, resultCode, data);
750             return;
751         }
752         mPendingActivityResult = null;
753 
754         // Reset the startActivity waiting flag
755         final PendingRequestArgs requestArgs = mPendingRequestArgs;
756         setWaitingForResult(null);
757         if (requestArgs == null) {
758             return;
759         }
760 
761         final int pendingAddWidgetId = requestArgs.getWidgetId();
762 
763         Runnable exitSpringLoaded = new Runnable() {
764             @Override
765             public void run() {
766                 mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
767             }
768         };
769 
770         if (requestCode == REQUEST_BIND_APPWIDGET) {
771             // This is called only if the user did not previously have permissions to bind widgets
772             final int appWidgetId = data != null ?
773                     data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) : -1;
774             if (resultCode == RESULT_CANCELED) {
775                 completeTwoStageWidgetDrop(RESULT_CANCELED, appWidgetId, requestArgs);
776                 mWorkspace.removeExtraEmptyScreenDelayed(
777                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
778             } else if (resultCode == RESULT_OK) {
779                 addAppWidgetImpl(
780                         appWidgetId, requestArgs, null,
781                         requestArgs.getWidgetHandler(),
782                         ON_ACTIVITY_RESULT_ANIMATION_DELAY);
783             }
784             return;
785         }
786 
787         boolean isWidgetDrop = (requestCode == REQUEST_PICK_APPWIDGET ||
788                 requestCode == REQUEST_CREATE_APPWIDGET);
789 
790         // We have special handling for widgets
791         if (isWidgetDrop) {
792             final int appWidgetId;
793             int widgetId = data != null ? data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
794                     : -1;
795             if (widgetId < 0) {
796                 appWidgetId = pendingAddWidgetId;
797             } else {
798                 appWidgetId = widgetId;
799             }
800 
801             final int result;
802             if (appWidgetId < 0 || resultCode == RESULT_CANCELED) {
803                 Log.e(TAG, "Error: appWidgetId (EXTRA_APPWIDGET_ID) was not " +
804                         "returned from the widget configuration activity.");
805                 result = RESULT_CANCELED;
806                 completeTwoStageWidgetDrop(result, appWidgetId, requestArgs);
807                 mWorkspace.removeExtraEmptyScreenDelayed(
808                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false,
809                         () -> getStateManager().goToState(NORMAL));
810             } else {
811                 if (requestArgs.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
812                     // When the screen id represents an actual screen (as opposed to a rank)
813                     // we make sure that the drop page actually exists.
814                     requestArgs.screenId =
815                             ensurePendingDropLayoutExists(requestArgs.screenId);
816                 }
817                 final CellLayout dropLayout =
818                         mWorkspace.getScreenWithId(requestArgs.screenId);
819 
820                 dropLayout.setDropPending(true);
821                 final Runnable onComplete = new Runnable() {
822                     @Override
823                     public void run() {
824                         completeTwoStageWidgetDrop(resultCode, appWidgetId, requestArgs);
825                         dropLayout.setDropPending(false);
826                     }
827                 };
828                 mWorkspace.removeExtraEmptyScreenDelayed(
829                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, onComplete);
830             }
831             return;
832         }
833 
834         if (requestCode == REQUEST_RECONFIGURE_APPWIDGET
835                 || requestCode == REQUEST_BIND_PENDING_APPWIDGET) {
836             if (resultCode == RESULT_OK) {
837                 // Update the widget view.
838                 completeAdd(requestCode, data, pendingAddWidgetId, requestArgs);
839             }
840             // Leave the widget in the pending state if the user canceled the configure.
841             return;
842         }
843 
844         if (requestCode == REQUEST_CREATE_SHORTCUT) {
845             // Handle custom shortcuts created using ACTION_CREATE_SHORTCUT.
846             if (resultCode == RESULT_OK && requestArgs.container != ItemInfo.NO_ID) {
847                 completeAdd(requestCode, data, -1, requestArgs);
848                 mWorkspace.removeExtraEmptyScreenDelayed(
849                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
850 
851             } else if (resultCode == RESULT_CANCELED) {
852                 mWorkspace.removeExtraEmptyScreenDelayed(
853                         ON_ACTIVITY_RESULT_ANIMATION_DELAY, false, exitSpringLoaded);
854             }
855         }
856 
857         mDragLayer.clearAnimatedView();
858     }
859 
860     @Override
onActivityResult( final int requestCode, final int resultCode, final Intent data)861     public void onActivityResult(
862             final int requestCode, final int resultCode, final Intent data) {
863         mPendingActivityRequestCode = -1;
864         handleActivityResult(requestCode, resultCode, data);
865     }
866 
867     @Override
onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)868     public void onRequestPermissionsResult(int requestCode, String[] permissions,
869             int[] grantResults) {
870         PendingRequestArgs pendingArgs = mPendingRequestArgs;
871         if (requestCode == REQUEST_PERMISSION_CALL_PHONE && pendingArgs != null
872                 && pendingArgs.getRequestCode() == REQUEST_PERMISSION_CALL_PHONE) {
873             setWaitingForResult(null);
874 
875             View v = null;
876             CellLayout layout = getCellLayout(pendingArgs.container, pendingArgs.screenId);
877             if (layout != null) {
878                 v = layout.getChildAt(pendingArgs.cellX, pendingArgs.cellY);
879             }
880             Intent intent = pendingArgs.getPendingIntent();
881 
882             if (grantResults.length > 0
883                     && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
884                 startActivitySafely(v, intent, null);
885             } else {
886                 // TODO: Show a snack bar with link to settings
887                 Toast.makeText(this, getString(R.string.msg_no_phone_permission,
888                         getString(R.string.derived_app_name)), Toast.LENGTH_SHORT).show();
889             }
890         }
891     }
892 
893     /**
894      * Check to see if a given screen id exists. If not, create it at the end, return the new id.
895      *
896      * @param screenId the screen id to check
897      * @return the new screen, or screenId if it exists
898      */
ensurePendingDropLayoutExists(int screenId)899     private int ensurePendingDropLayoutExists(int screenId) {
900         CellLayout dropLayout = mWorkspace.getScreenWithId(screenId);
901         if (dropLayout == null) {
902             // it's possible that the add screen was removed because it was
903             // empty and a re-bind occurred
904             mWorkspace.addExtraEmptyScreens();
905             IntSet emptyPagesAdded = mWorkspace.commitExtraEmptyScreens();
906             return emptyPagesAdded.isEmpty() ? -1 : emptyPagesAdded.getArray().get(0);
907         }
908         return screenId;
909     }
910 
911     @Thunk
completeTwoStageWidgetDrop( final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs)912     void completeTwoStageWidgetDrop(
913             final int resultCode, final int appWidgetId, final PendingRequestArgs requestArgs) {
914         CellLayout cellLayout = mWorkspace.getScreenWithId(requestArgs.screenId);
915         Runnable onCompleteRunnable = null;
916         int animationType = 0;
917 
918         AppWidgetHostView boundWidget = null;
919         if (resultCode == RESULT_OK) {
920             animationType = Workspace.COMPLETE_TWO_STAGE_WIDGET_DROP_ANIMATION;
921             final AppWidgetHostView layout = mAppWidgetHost.createView(this, appWidgetId,
922                     requestArgs.getWidgetHandler().getProviderInfo(this));
923             boundWidget = layout;
924             onCompleteRunnable = new Runnable() {
925                 @Override
926                 public void run() {
927                     completeAddAppWidget(appWidgetId, requestArgs, layout, null);
928                     mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
929                 }
930             };
931         } else if (resultCode == RESULT_CANCELED) {
932             mAppWidgetHost.deleteAppWidgetId(appWidgetId);
933             animationType = Workspace.CANCEL_TWO_STAGE_WIDGET_DROP_ANIMATION;
934         }
935         if (mDragLayer.getAnimatedView() != null) {
936             mWorkspace.animateWidgetDrop(requestArgs, cellLayout,
937                     (DragView) mDragLayer.getAnimatedView(), onCompleteRunnable,
938                     animationType, boundWidget, true);
939         } else if (onCompleteRunnable != null) {
940             // The animated view may be null in the case of a rotation during widget configuration
941             onCompleteRunnable.run();
942         }
943     }
944 
945     @Override
onStop()946     protected void onStop() {
947         super.onStop();
948         if (mDeferOverlayCallbacks) {
949             checkIfOverlayStillDeferred();
950         } else {
951             mOverlayManager.onActivityStopped(this);
952         }
953         hideKeyboard();
954         logStopAndResume(false /* isResume */);
955         mAppWidgetHost.setActivityStarted(false);
956         NotificationListener.removeNotificationsChangedListener();
957     }
958 
959     @Override
onStart()960     protected void onStart() {
961         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_START_EVT,
962                 TraceHelper.FLAG_UI_EVENT);
963         super.onStart();
964         if (!mDeferOverlayCallbacks) {
965             mOverlayManager.onActivityStarted(this);
966         }
967 
968         mAppWidgetHost.setActivityStarted(true);
969         TraceHelper.INSTANCE.endSection(traceToken);
970     }
971 
972     @Override
973     @CallSuper
onDeferredResumed()974     protected void onDeferredResumed() {
975         logStopAndResume(true /* isResume */);
976 
977         // Process any items that were added while Launcher was away.
978         ItemInstallQueue.INSTANCE.get(this)
979                 .resumeModelPush(FLAG_ACTIVITY_PAUSED);
980 
981         // Refresh shortcuts if the permission changed.
982         mModel.validateModelDataOnResume();
983 
984         // Set the notification listener and fetch updated notifications when we resume
985         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
986 
987         DiscoveryBounce.showForHomeIfNeeded(this);
988         mAppWidgetHost.setActivityResumed(true);
989     }
990 
logStopAndResume(boolean isResume)991     private void logStopAndResume(boolean isResume) {
992         if (mPendingExecutor != null) return;
993         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
994         int statsLogOrdinal = mStateManager.getState().statsLogOrdinal;
995 
996         StatsLogManager.EventEnum event;
997         StatsLogManager.StatsLogger logger = getStatsLogManager().logger();
998         if (isResume) {
999             logger.withSrcState(LAUNCHER_STATE_BACKGROUND)
1000                 .withDstState(mStateManager.getState().statsLogOrdinal);
1001             event = LAUNCHER_ONRESUME;
1002         } else { /* command == Action.Command.STOP */
1003             logger.withSrcState(mStateManager.getState().statsLogOrdinal)
1004                     .withDstState(LAUNCHER_STATE_BACKGROUND);
1005             event = LAUNCHER_ONSTOP;
1006         }
1007 
1008         if (statsLogOrdinal == LAUNCHER_STATE_HOME && mWorkspace != null) {
1009             logger.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
1010                     .setWorkspace(
1011                             LauncherAtom.WorkspaceContainer.newBuilder()
1012                                     .setPageIndex(pageIndex)).build());
1013         }
1014         logger.log(event);
1015     }
1016 
scheduleDeferredCheck()1017     private void scheduleDeferredCheck() {
1018         mHandler.removeCallbacks(mDeferredOverlayCallbacks);
1019         postAsyncCallback(mHandler, mDeferredOverlayCallbacks);
1020     }
1021 
checkIfOverlayStillDeferred()1022     private void checkIfOverlayStillDeferred() {
1023         if (!mDeferOverlayCallbacks) {
1024             return;
1025         }
1026         if (isStarted() && (!hasBeenResumed()
1027                 || mStateManager.getState().hasFlag(FLAG_NON_INTERACTIVE))) {
1028             return;
1029         }
1030         mDeferOverlayCallbacks = false;
1031 
1032         // Move the client to the correct state. Calling the same method twice is no-op.
1033         if (isStarted()) {
1034             mOverlayManager.onActivityStarted(this);
1035         }
1036         if (hasBeenResumed()) {
1037             mOverlayManager.onActivityResumed(this);
1038         } else {
1039             mOverlayManager.onActivityPaused(this);
1040         }
1041         if (!isStarted()) {
1042             mOverlayManager.onActivityStopped(this);
1043         }
1044     }
1045 
deferOverlayCallbacksUntilNextResumeOrStop()1046     public void deferOverlayCallbacksUntilNextResumeOrStop() {
1047         mDeferOverlayCallbacks = true;
1048     }
1049 
getOverlayManager()1050     public LauncherOverlayManager getOverlayManager() {
1051         return mOverlayManager;
1052     }
1053 
1054     @Override
onStateSetStart(LauncherState state)1055     public void onStateSetStart(LauncherState state) {
1056         super.onStateSetStart(state);
1057         if (mDeferOverlayCallbacks) {
1058             scheduleDeferredCheck();
1059         }
1060         addActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
1061 
1062         if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
1063             AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
1064         }
1065 
1066         if (state == SPRING_LOADED) {
1067             // Prevent any Un/InstallShortcutReceivers from updating the db while we are
1068             // not on homescreen
1069             ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
1070             getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
1071 
1072             mWorkspace.showPageIndicatorAtCurrentScroll();
1073             mWorkspace.setClipChildren(false);
1074         }
1075         // When multiple pages are visible, show persistent page indicator
1076         mWorkspace.getPageIndicator().setShouldAutoHide(!state.hasFlag(FLAG_MULTI_PAGE));
1077 
1078         mPrevLauncherState = mStateManager.getCurrentStableState();
1079         if (mPrevLauncherState != state && ALL_APPS.equals(state)
1080                 // Making sure mAllAppsSessionLogId is null to avoid double logging.
1081                 && mAllAppsSessionLogId == null) {
1082             // creates new instance ID since new all apps session is started.
1083             mAllAppsSessionLogId = new InstanceIdSequence().newInstanceId();
1084             getStatsLogManager()
1085                     .logger()
1086                     .log(FeatureFlags.ENABLE_DEVICE_SEARCH.get()
1087                             ? LAUNCHER_ALLAPPS_ENTRY_WITH_DEVICE_SEARCH
1088                             : LAUNCHER_ALLAPPS_ENTRY);
1089         }
1090     }
1091 
1092     @Override
onStateSetEnd(LauncherState state)1093     public void onStateSetEnd(LauncherState state) {
1094         super.onStateSetEnd(state);
1095         getAppWidgetHost().setStateIsNormal(state == LauncherState.NORMAL);
1096         getWorkspace().setClipChildren(!state.hasFlag(FLAG_MULTI_PAGE));
1097 
1098         finishAutoCancelActionMode();
1099         removeActivityFlags(ACTIVITY_STATE_TRANSITION_ACTIVE);
1100 
1101         // dispatch window state changed
1102         getWindow().getDecorView().sendAccessibilityEvent(TYPE_WINDOW_STATE_CHANGED);
1103         AccessibilityManagerCompat.sendStateEventToTest(this, state.ordinal);
1104 
1105         if (state == NORMAL) {
1106             // Re-enable any Un/InstallShortcutReceiver and now process any queued items
1107             ItemInstallQueue.INSTANCE.get(this)
1108                     .resumeModelPush(FLAG_DRAG_AND_DROP);
1109 
1110             // Clear any rotation locks when going to normal state
1111             getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
1112         }
1113 
1114         if (ALL_APPS.equals(mPrevLauncherState) && !ALL_APPS.equals(state)
1115                 // Making sure mAllAppsSessionLogId is not null to avoid double logging.
1116                 && mAllAppsSessionLogId != null) {
1117             getAppsView().reset(false);
1118             getStatsLogManager().logger()
1119                     .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
1120                             .setWorkspace(
1121                                     LauncherAtom.WorkspaceContainer.newBuilder()
1122                                             .setPageIndex(getWorkspace().getCurrentPage()))
1123                             .build())
1124                     .log(LAUNCHER_ALLAPPS_EXIT);
1125             mAllAppsSessionLogId = null;
1126         }
1127     }
1128 
1129     @Override
onResume()1130     protected void onResume() {
1131         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_RESUME_EVT,
1132                 TraceHelper.FLAG_UI_EVENT);
1133         super.onResume();
1134 
1135         if (mDeferOverlayCallbacks) {
1136             scheduleDeferredCheck();
1137         } else {
1138             mOverlayManager.onActivityResumed(this);
1139         }
1140 
1141         TraceHelper.INSTANCE.endSection(traceToken);
1142     }
1143 
1144     @Override
onPause()1145     protected void onPause() {
1146         // Ensure that items added to Launcher are queued until Launcher returns
1147         ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
1148 
1149         super.onPause();
1150         mDragController.cancelDrag();
1151         mLastTouchUpTime = -1;
1152         mDropTargetBar.animateToVisibility(false);
1153 
1154         if (!mDeferOverlayCallbacks) {
1155             mOverlayManager.onActivityPaused(this);
1156         }
1157         mAppWidgetHost.setActivityResumed(false);
1158     }
1159 
1160     /**
1161      * {@code LauncherOverlayCallbacks} scroll amount.
1162      * Indicates transition progress to -1 screen.
1163      * @param progress From 0 to 1.
1164      */
1165     @Override
onScrollChanged(float progress)1166     public void onScrollChanged(float progress) {
1167         if (mWorkspace != null) {
1168             mWorkspace.onOverlayScrollChanged(progress);
1169         }
1170     }
1171 
1172     /**
1173      * Restores the previous state, if it exists.
1174      *
1175      * @param savedState The previous state.
1176      */
restoreState(Bundle savedState)1177     private void restoreState(Bundle savedState) {
1178         if (savedState == null) {
1179             return;
1180         }
1181 
1182         int stateOrdinal = savedState.getInt(RUNTIME_STATE, NORMAL.ordinal);
1183         LauncherState[] stateValues = LauncherState.values();
1184         LauncherState state = stateValues[stateOrdinal];
1185 
1186         NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
1187         boolean forceRestore = lastInstance != null
1188                 && (lastInstance.config.diff(mOldConfig) & CONFIG_UI_MODE) != 0;
1189         if (forceRestore || !state.shouldDisableRestore()) {
1190             mStateManager.goToState(state, false /* animated */);
1191         }
1192 
1193         PendingRequestArgs requestArgs = savedState.getParcelable(
1194                 RUNTIME_STATE_PENDING_REQUEST_ARGS);
1195         if (requestArgs != null) {
1196             setWaitingForResult(requestArgs);
1197         }
1198         mPendingActivityRequestCode = savedState.getInt(RUNTIME_STATE_PENDING_REQUEST_CODE);
1199 
1200         mPendingActivityResult = savedState.getParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT);
1201 
1202         SparseArray<Parcelable> widgetsState =
1203                 savedState.getSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL);
1204         if (widgetsState != null) {
1205             WidgetsFullSheet.show(this, false).restoreHierarchyState(widgetsState);
1206         }
1207     }
1208 
1209     /**
1210      * Finds all the views we need and configure them properly.
1211      */
setupViews()1212     protected void setupViews() {
1213         mDragLayer = findViewById(R.id.drag_layer);
1214         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
1215         mWorkspace = mDragLayer.findViewById(R.id.workspace);
1216         mWorkspace.initParentViews(mDragLayer);
1217         mOverviewPanel = findViewById(R.id.overview_panel);
1218         mHotseat = findViewById(R.id.hotseat);
1219         mHotseat.setWorkspace(mWorkspace);
1220 
1221         // Setup the drag layer
1222         mDragLayer.setup(mDragController, mWorkspace);
1223 
1224         mWorkspace.setup(mDragController);
1225         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
1226         // default state, otherwise we will update to the wrong offsets in RTL
1227         mWorkspace.lockWallpaperToDefaultPage();
1228         mWorkspace.bindAndInitFirstWorkspaceScreen();
1229         mDragController.addDragListener(mWorkspace);
1230 
1231         // Get the search/delete/uninstall bar
1232         mDropTargetBar = mDragLayer.findViewById(R.id.drop_target_bar);
1233 
1234         // Setup Apps
1235         mAppsView = findViewById(R.id.apps_view);
1236 
1237         // Setup Scrim
1238         mScrimView = findViewById(R.id.scrim_view);
1239 
1240         // Setup the drag controller (drop targets have to be added in reverse order in priority)
1241         mDropTargetBar.setup(mDragController);
1242         mAllAppsController.setupViews(mScrimView, mAppsView);
1243     }
1244 
1245     /**
1246      * Creates a view representing a shortcut.
1247      *
1248      * @param info The data structure describing the shortcut.
1249      */
createShortcut(WorkspaceItemInfo info)1250     View createShortcut(WorkspaceItemInfo info) {
1251         return createShortcut((ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentPage()), info);
1252     }
1253 
1254     /**
1255      * Creates a view representing a shortcut inflated from the specified resource.
1256      *
1257      * @param parent The group the shortcut belongs to.
1258      * @param info   The data structure describing the shortcut.
1259      * @return A View inflated from layoutResId.
1260      */
createShortcut(ViewGroup parent, WorkspaceItemInfo info)1261     public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
1262         BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
1263                 .inflate(R.layout.app_icon, parent, false);
1264         favorite.applyFromWorkspaceItem(info);
1265         favorite.setOnClickListener(ItemClickHandler.INSTANCE);
1266         favorite.setOnFocusChangeListener(mFocusHandler);
1267         return favorite;
1268     }
1269 
1270     /**
1271      * Add a shortcut to the workspace or to a Folder.
1272      *
1273      * @param data The intent describing the shortcut.
1274      */
completeAddShortcut(Intent data, int container, int screenId, int cellX, int cellY, PendingRequestArgs args)1275     protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
1276             int cellY, PendingRequestArgs args) {
1277         if (args.getRequestCode() != REQUEST_CREATE_SHORTCUT
1278                 || args.getPendingIntent().getComponent() == null) {
1279             return;
1280         }
1281 
1282         int[] cellXY = mTmpAddItemCellCoordinates;
1283         CellLayout layout = getCellLayout(container, screenId);
1284 
1285         WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
1286                     this, PinRequestHelper.getPinItemRequest(data), 0);
1287 
1288         if (info == null) {
1289             // Legacy shortcuts are only supported for primary profile.
1290             info = Process.myUserHandle().equals(args.user)
1291                     ? ModelUtils.fromLegacyShortcutIntent(this, data) : null;
1292 
1293             if (info == null) {
1294                 Log.e(TAG, "Unable to parse a valid custom shortcut result");
1295                 return;
1296             } else if (!new PackageManagerHelper(this).hasPermissionForActivity(
1297                     info.intent, args.getPendingIntent().getComponent().getPackageName())) {
1298                 // The app is trying to add a shortcut without sufficient permissions
1299                 Log.e(TAG, "Ignoring malicious intent " + info.intent.toUri(0));
1300                 return;
1301             }
1302         }
1303 
1304         if (container < 0) {
1305             // Adding a shortcut to the Workspace.
1306             final View view = createShortcut(info);
1307             boolean foundCellSpan = false;
1308             // First we check if we already know the exact location where we want to add this item.
1309             if (cellX >= 0 && cellY >= 0) {
1310                 cellXY[0] = cellX;
1311                 cellXY[1] = cellY;
1312                 foundCellSpan = true;
1313 
1314                 DragObject dragObject = new DragObject(getApplicationContext());
1315                 dragObject.dragInfo = info;
1316                 // If appropriate, either create a folder or add to an existing folder
1317                 if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
1318                         true, dragObject)) {
1319                     return;
1320                 }
1321                 if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
1322                         true)) {
1323                     return;
1324                 }
1325             } else {
1326                 foundCellSpan = layout.findCellForSpan(cellXY, 1, 1);
1327             }
1328 
1329             if (!foundCellSpan) {
1330                 mWorkspace.onNoCellFound(layout, info, /* logInstanceId= */ null);
1331                 return;
1332             }
1333 
1334             getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
1335             mWorkspace.addInScreen(view, info);
1336         } else {
1337             // Adding a shortcut to a Folder.
1338             FolderIcon folderIcon = findFolderIcon(container);
1339             if (folderIcon != null) {
1340                 FolderInfo folderInfo = (FolderInfo) folderIcon.getTag();
1341                 folderInfo.add(info, args.rank, false);
1342             } else {
1343                 Log.e(TAG, "Could not find folder with id " + container + " to add shortcut.");
1344             }
1345         }
1346     }
1347 
1348     @Override
findFolderIcon(final int folderIconId)1349     public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
1350         return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
1351     }
1352 
1353     /**
1354      * Add a widget to the workspace.
1355      *
1356      * @param appWidgetId The app widget id
1357      */
1358     @Thunk
completeAddAppWidget(int appWidgetId, ItemInfo itemInfo, AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo)1359     void completeAddAppWidget(int appWidgetId, ItemInfo itemInfo,
1360             AppWidgetHostView hostView, LauncherAppWidgetProviderInfo appWidgetInfo) {
1361 
1362         if (appWidgetInfo == null) {
1363             appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(appWidgetId);
1364         }
1365 
1366         if (hostView == null) {
1367             // Perform actual inflation because we're live
1368             hostView = mAppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
1369         }
1370 
1371         LauncherAppWidgetInfo launcherInfo;
1372         launcherInfo =
1373                 new LauncherAppWidgetInfo(
1374                         appWidgetId, appWidgetInfo.provider, appWidgetInfo, hostView);
1375         launcherInfo.spanX = itemInfo.spanX;
1376         launcherInfo.spanY = itemInfo.spanY;
1377         launcherInfo.minSpanX = itemInfo.minSpanX;
1378         launcherInfo.minSpanY = itemInfo.minSpanY;
1379         launcherInfo.user = appWidgetInfo.getProfile();
1380         if (itemInfo instanceof PendingAddWidgetInfo) {
1381             launcherInfo.sourceContainer = ((PendingAddWidgetInfo) itemInfo).sourceContainer;
1382         } else if (itemInfo instanceof PendingRequestArgs) {
1383             launcherInfo.sourceContainer =
1384                     ((PendingRequestArgs) itemInfo).getWidgetSourceContainer();
1385         }
1386 
1387         getModelWriter().addItemToDatabase(launcherInfo,
1388                 itemInfo.container, itemInfo.screenId, itemInfo.cellX, itemInfo.cellY);
1389 
1390         hostView.setVisibility(View.VISIBLE);
1391         prepareAppWidget(hostView, launcherInfo);
1392         mWorkspace.addInScreen(hostView, launcherInfo);
1393         announceForAccessibility(R.string.item_added_to_workspace);
1394 
1395         // Show the widget resize frame.
1396         if (hostView instanceof LauncherAppWidgetHostView) {
1397             final LauncherAppWidgetHostView launcherHostView = (LauncherAppWidgetHostView) hostView;
1398             CellLayout cellLayout = getCellLayout(launcherInfo.container, launcherInfo.screenId);
1399             if (mStateManager.getState() == NORMAL) {
1400                 AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
1401             } else {
1402                 mStateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
1403                     @Override
1404                     public void onStateTransitionComplete(LauncherState finalState) {
1405                         if (mPrevLauncherState == SPRING_LOADED && finalState == NORMAL) {
1406                             AppWidgetResizeFrame.showForWidget(launcherHostView, cellLayout);
1407                             mStateManager.removeStateListener(this);
1408                         }
1409                     }
1410                 });
1411             }
1412         }
1413     }
1414 
prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item)1415     private void prepareAppWidget(AppWidgetHostView hostView, LauncherAppWidgetInfo item) {
1416         hostView.setTag(item);
1417         item.onBindAppWidget(this, hostView);
1418         hostView.setFocusable(true);
1419         hostView.setOnFocusChangeListener(mFocusHandler);
1420     }
1421 
1422     private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
1423         @Override
1424         public void onReceive(Context context, Intent intent) {
1425             onScreenOff();
1426         }
1427     };
1428 
updateNotificationDots(Predicate<PackageUserKey> updatedDots)1429     private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
1430         mWorkspace.updateNotificationDots(updatedDots);
1431         mAppsView.getAppsStore().updateNotificationDots(updatedDots);
1432     }
1433 
1434     @Override
onAttachedToWindow()1435     public void onAttachedToWindow() {
1436         super.onAttachedToWindow();
1437         mOverlayManager.onAttachedToWindow();
1438     }
1439 
1440     @Override
onDetachedFromWindow()1441     public void onDetachedFromWindow() {
1442         super.onDetachedFromWindow();
1443         mOverlayManager.onDetachedFromWindow();
1444         closeContextMenu();
1445     }
1446 
1447     @Override
onRetainNonConfigurationInstance()1448     public Object onRetainNonConfigurationInstance() {
1449         NonConfigInstance instance = new NonConfigInstance();
1450         instance.config = new Configuration(mOldConfig);
1451 
1452         int width = mDragLayer.getWidth();
1453         int height = mDragLayer.getHeight();
1454 
1455         if (FeatureFlags.ENABLE_LAUNCHER_ACTIVITY_THEME_CROSSFADE.get()
1456                 && width > 0
1457                 && height > 0) {
1458             instance.snapshot =
1459                     BitmapRenderer.createHardwareBitmap(width, height, mDragLayer::draw);
1460         }
1461         return instance;
1462     }
1463 
getAllAppsController()1464     public AllAppsTransitionController getAllAppsController() {
1465         return mAllAppsController;
1466     }
1467 
1468     @Override
getDragLayer()1469     public DragLayer getDragLayer() {
1470         return mDragLayer;
1471     }
1472 
getAppsView()1473     public AllAppsContainerView getAppsView() {
1474         return mAppsView;
1475     }
1476 
getWorkspace()1477     public Workspace getWorkspace() {
1478         return mWorkspace;
1479     }
1480 
getHotseat()1481     public Hotseat getHotseat() {
1482         return mHotseat;
1483     }
1484 
getOverviewPanel()1485     public <T extends View> T getOverviewPanel() {
1486         return (T) mOverviewPanel;
1487     }
1488 
getDropTargetBar()1489     public DropTargetBar getDropTargetBar() {
1490         return mDropTargetBar;
1491     }
1492 
1493     @Override
getScrimView()1494     public ScrimView getScrimView() {
1495         return mScrimView;
1496     }
1497 
getAppWidgetHost()1498     public LauncherAppWidgetHost getAppWidgetHost() {
1499         return mAppWidgetHost;
1500     }
1501 
createAppWidgetHost()1502     protected LauncherAppWidgetHost createAppWidgetHost() {
1503         return new LauncherAppWidgetHost(this,
1504                 appWidgetId -> getWorkspace().removeWidget(appWidgetId));
1505     }
1506 
getModel()1507     public LauncherModel getModel() {
1508         return mModel;
1509     }
1510 
getModelWriter()1511     public ModelWriter getModelWriter() {
1512         return mModelWriter;
1513     }
1514 
1515     @Override
getSharedPrefs()1516     public SharedPreferences getSharedPrefs() {
1517         return mSharedPrefs;
1518     }
1519 
1520     @Override
getDevicePrefs()1521     public SharedPreferences getDevicePrefs() {
1522         return Utilities.getDevicePrefs(this);
1523     }
1524 
getOrientation()1525     public int getOrientation() {
1526         return mOldConfig.orientation;
1527     }
1528 
1529     @Override
onNewIntent(Intent intent)1530     protected void onNewIntent(Intent intent) {
1531         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
1532             Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Launcher.onNewIntent: " + intent);
1533         }
1534         Object traceToken = TraceHelper.INSTANCE.beginSection(ON_NEW_INTENT_EVT);
1535         super.onNewIntent(intent);
1536 
1537         boolean alreadyOnHome = hasWindowFocus() && ((intent.getFlags() &
1538                 Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT)
1539                 != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
1540 
1541         // Check this condition before handling isActionMain, as this will get reset.
1542         boolean shouldMoveToDefaultScreen = alreadyOnHome && isInState(NORMAL)
1543                 && AbstractFloatingView.getTopOpenView(this) == null;
1544         boolean isActionMain = Intent.ACTION_MAIN.equals(intent.getAction());
1545         boolean internalStateHandled = ACTIVITY_TRACKER.handleNewIntent(this);
1546         hideKeyboard();
1547         if (isActionMain) {
1548             if (!internalStateHandled) {
1549                 // In all these cases, only animate if we're already on home
1550                 closeOpenViews(isStarted());
1551 
1552                 if (!isInState(NORMAL)) {
1553                     // Only change state, if not already the same. This prevents cancelling any
1554                     // animations running as part of resume
1555                     mStateManager.goToState(NORMAL, mStateManager.shouldAnimateStateChange());
1556                 }
1557 
1558                 // Reset the apps view
1559                 if (!alreadyOnHome) {
1560                     mAppsView.reset(isStarted() /* animate */);
1561                 }
1562 
1563                 if (shouldMoveToDefaultScreen && !mWorkspace.isHandlingTouch()) {
1564                     mWorkspace.post(mWorkspace::moveToDefaultScreen);
1565                 }
1566             }
1567 
1568             if (mLauncherCallbacks != null) {
1569                 mLauncherCallbacks.onHomeIntent(internalStateHandled);
1570             }
1571             mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
1572             handleGestureContract(intent);
1573         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
1574             showAllAppsFromIntent(alreadyOnHome);
1575         }
1576 
1577         TraceHelper.INSTANCE.endSection(traceToken);
1578     }
1579 
showAllAppsFromIntent(boolean alreadyOnHome)1580     protected void showAllAppsFromIntent(boolean alreadyOnHome) {
1581         AbstractFloatingView.closeAllOpenViews(this);
1582         getStateManager().goToState(ALL_APPS, alreadyOnHome);
1583     }
1584 
1585     /**
1586      * Handles gesture nav contract
1587      */
handleGestureContract(Intent intent)1588     protected void handleGestureContract(Intent intent) {
1589         GestureNavContract gnc = GestureNavContract.fromIntent(intent);
1590         if (gnc != null) {
1591             AbstractFloatingView.closeOpenViews(this, false, TYPE_ICON_SURFACE);
1592             FloatingSurfaceView.show(this, gnc);
1593         }
1594     }
1595 
1596     /**
1597      * Hides the keyboard if visible
1598      */
hideKeyboard()1599     public void hideKeyboard() {
1600         final View v = getWindow().peekDecorView();
1601         if (v != null && v.getWindowToken() != null) {
1602             UiThreadHelper.hideKeyboardAsync(this, v.getWindowToken());
1603         }
1604     }
1605 
1606     @Override
onRestoreInstanceState(Bundle state)1607     public void onRestoreInstanceState(Bundle state) {
1608         super.onRestoreInstanceState(state);
1609         if (mSynchronouslyBoundPages != null) {
1610             mSynchronouslyBoundPages.forEach(screenId -> {
1611                 int pageIndex = mWorkspace.getPageIndexForScreenId(screenId);
1612                 if (pageIndex != PagedView.INVALID_PAGE) {
1613                     mWorkspace.restoreInstanceStateForChild(pageIndex);
1614                 }
1615             });
1616         }
1617     }
1618 
1619     @Override
onSaveInstanceState(Bundle outState)1620     protected void onSaveInstanceState(Bundle outState) {
1621         outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
1622                 mWorkspace.getCurrentPageScreenIds().getArray().toArray());
1623         outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
1624 
1625         AbstractFloatingView widgets = AbstractFloatingView
1626                 .getOpenView(this, AbstractFloatingView.TYPE_WIDGETS_FULL_SHEET);
1627         if (widgets != null) {
1628             SparseArray<Parcelable> widgetsState = new SparseArray<>();
1629             widgets.saveHierarchyState(widgetsState);
1630             outState.putSparseParcelableArray(RUNTIME_STATE_WIDGET_PANEL, widgetsState);
1631         } else {
1632             outState.remove(RUNTIME_STATE_WIDGET_PANEL);
1633         }
1634 
1635         // We close any open folders and shortcut containers that are not safe for rebind,
1636         // and we need to make sure this state is reflected.
1637         AbstractFloatingView.closeOpenViews(this, false, TYPE_ALL & ~TYPE_REBIND_SAFE);
1638         finishAutoCancelActionMode();
1639 
1640         DragView.removeAllViews(this);
1641 
1642         if (mPendingRequestArgs != null) {
1643             outState.putParcelable(RUNTIME_STATE_PENDING_REQUEST_ARGS, mPendingRequestArgs);
1644         }
1645         outState.putInt(RUNTIME_STATE_PENDING_REQUEST_CODE, mPendingActivityRequestCode);
1646 
1647         if (mPendingActivityResult != null) {
1648             outState.putParcelable(RUNTIME_STATE_PENDING_ACTIVITY_RESULT, mPendingActivityResult);
1649         }
1650 
1651         super.onSaveInstanceState(outState);
1652         mOverlayManager.onActivitySaveInstanceState(this, outState);
1653     }
1654 
1655     @Override
onDestroy()1656     public void onDestroy() {
1657         super.onDestroy();
1658         ACTIVITY_TRACKER.onActivityDestroyed(this);
1659 
1660         unregisterReceiver(mScreenOffReceiver);
1661         mWorkspace.removeFolderListeners();
1662         PluginManagerWrapper.INSTANCE.get(this).removePluginListener(this);
1663 
1664         mModel.removeCallbacks(this);
1665         mRotationHelper.destroy();
1666 
1667         try {
1668             mAppWidgetHost.stopListening();
1669         } catch (NullPointerException ex) {
1670             Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
1671         }
1672 
1673         TextKeyListener.getInstance().release();
1674         clearPendingBinds();
1675         LauncherAppState.getIDP(this).removeOnChangeListener(this);
1676 
1677         mOverlayManager.onActivityDestroyed(this);
1678         mUserChangedCallbackCloseable.close();
1679     }
1680 
getAccessibilityDelegate()1681     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
1682         return mAccessibilityDelegate;
1683     }
1684 
getDragController()1685     public DragController getDragController() {
1686         return mDragController;
1687     }
1688 
1689     @Override
startActivityForResult(Intent intent, int requestCode, Bundle options)1690     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
1691         if (requestCode != -1) {
1692             mPendingActivityRequestCode = requestCode;
1693         }
1694         super.startActivityForResult(intent, requestCode, options);
1695     }
1696 
1697     @Override
startIntentSenderForResult(IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)1698     public void startIntentSenderForResult(IntentSender intent, int requestCode,
1699             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
1700         if (requestCode != -1) {
1701             mPendingActivityRequestCode = requestCode;
1702         }
1703         try {
1704             super.startIntentSenderForResult(intent, requestCode,
1705                     fillInIntent, flagsMask, flagsValues, extraFlags, options);
1706         } catch (IntentSender.SendIntentException e) {
1707             throw new ActivityNotFoundException();
1708         }
1709     }
1710 
1711     /**
1712      * Indicates that we want global search for this activity by setting the globalSearch
1713      * argument for {@link #startSearch} to true.
1714      */
1715     @Override
startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch)1716     public void startSearch(String initialQuery, boolean selectInitialQuery,
1717             Bundle appSearchData, boolean globalSearch) {
1718         if (appSearchData == null) {
1719             appSearchData = new Bundle();
1720             appSearchData.putString("source", "launcher-search");
1721         }
1722 
1723         if (mLauncherCallbacks == null ||
1724                 !mLauncherCallbacks.startSearch(initialQuery, selectInitialQuery, appSearchData)) {
1725             // Starting search from the callbacks failed. Start the default global search.
1726             super.startSearch(initialQuery, selectInitialQuery, appSearchData, true);
1727         }
1728 
1729         // We need to show the workspace after starting the search
1730         mStateManager.goToState(NORMAL);
1731     }
1732 
isWorkspaceLocked()1733     public boolean isWorkspaceLocked() {
1734         return mWorkspaceLoading || mPendingRequestArgs != null;
1735     }
1736 
isWorkspaceLoading()1737     public boolean isWorkspaceLoading() {
1738         return mWorkspaceLoading;
1739     }
1740 
setWorkspaceLoading(boolean value)1741     private void setWorkspaceLoading(boolean value) {
1742         mWorkspaceLoading = value;
1743     }
1744 
setWaitingForResult(PendingRequestArgs args)1745     public void setWaitingForResult(PendingRequestArgs args) {
1746         mPendingRequestArgs = args;
1747     }
1748 
addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler)1749     void addAppWidgetFromDropImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget,
1750             WidgetAddFlowHandler addFlowHandler) {
1751         if (LOGD) {
1752             Log.d(TAG, "Adding widget from drop");
1753         }
1754         addAppWidgetImpl(appWidgetId, info, boundWidget, addFlowHandler, 0);
1755     }
1756 
addAppWidgetImpl(int appWidgetId, ItemInfo info, AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay)1757     void addAppWidgetImpl(int appWidgetId, ItemInfo info,
1758             AppWidgetHostView boundWidget, WidgetAddFlowHandler addFlowHandler, int delay) {
1759         if (!addFlowHandler.startConfigActivity(this, appWidgetId, info,
1760                 REQUEST_CREATE_APPWIDGET)) {
1761             // If the configuration flow was not started, add the widget
1762 
1763             Runnable onComplete = new Runnable() {
1764                 @Override
1765                 public void run() {
1766                     // Exit spring loaded mode if necessary after adding the widget
1767                     mStateManager.goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
1768                 }
1769             };
1770             completeAddAppWidget(appWidgetId, info, boundWidget,
1771                     addFlowHandler.getProviderInfo(this));
1772             mWorkspace.removeExtraEmptyScreenDelayed(delay, false, onComplete);
1773         }
1774     }
1775 
addPendingItem(PendingAddItemInfo info, int container, int screenId, int[] cell, int spanX, int spanY)1776     public void addPendingItem(PendingAddItemInfo info, int container, int screenId,
1777             int[] cell, int spanX, int spanY) {
1778         info.container = container;
1779         info.screenId = screenId;
1780         if (cell != null) {
1781             info.cellX = cell[0];
1782             info.cellY = cell[1];
1783         }
1784         info.spanX = spanX;
1785         info.spanY = spanY;
1786 
1787         switch (info.itemType) {
1788             case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
1789             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
1790                 addAppWidgetFromDrop((PendingAddWidgetInfo) info);
1791                 break;
1792             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
1793                 processShortcutFromDrop((PendingAddShortcutInfo) info);
1794                 break;
1795             default:
1796                 throw new IllegalStateException("Unknown item type: " + info.itemType);
1797         }
1798     }
1799 
1800     /**
1801      * Process a shortcut drop.
1802      */
processShortcutFromDrop(PendingAddShortcutInfo info)1803     private void processShortcutFromDrop(PendingAddShortcutInfo info) {
1804         Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(info.componentName);
1805         setWaitingForResult(PendingRequestArgs.forIntent(REQUEST_CREATE_SHORTCUT, intent, info));
1806         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: processShortcutFromDrop");
1807         if (!info.activityInfo.startConfigActivity(this, REQUEST_CREATE_SHORTCUT)) {
1808             handleActivityResult(REQUEST_CREATE_SHORTCUT, RESULT_CANCELED, null);
1809         }
1810     }
1811 
1812     /**
1813      * Process a widget drop.
1814      */
addAppWidgetFromDrop(PendingAddWidgetInfo info)1815     private void addAppWidgetFromDrop(PendingAddWidgetInfo info) {
1816         AppWidgetHostView hostView = info.boundWidget;
1817         final int appWidgetId;
1818         WidgetAddFlowHandler addFlowHandler = info.getHandler();
1819         if (hostView != null) {
1820             // In the case where we've prebound the widget, we remove it from the DragLayer
1821             if (LOGD) {
1822                 Log.d(TAG, "Removing widget view from drag layer and setting boundWidget to null");
1823             }
1824             getDragLayer().removeView(hostView);
1825 
1826             appWidgetId = hostView.getAppWidgetId();
1827             addAppWidgetFromDropImpl(appWidgetId, info, hostView, addFlowHandler);
1828 
1829             // Clear the boundWidget so that it doesn't get destroyed.
1830             info.boundWidget = null;
1831         } else {
1832             // In this case, we either need to start an activity to get permission to bind
1833             // the widget, or we need to start an activity to configure the widget, or both.
1834             if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET) {
1835                 appWidgetId = CustomWidgetManager.INSTANCE.get(this).getWidgetIdForCustomProvider(
1836                         info.componentName);
1837             } else {
1838                 appWidgetId = getAppWidgetHost().allocateAppWidgetId();
1839             }
1840             Bundle options = info.bindOptions;
1841 
1842             boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
1843                     appWidgetId, info.info, options);
1844             if (success) {
1845                 addAppWidgetFromDropImpl(appWidgetId, info, null, addFlowHandler);
1846             } else {
1847                 addFlowHandler.startBindFlow(this, appWidgetId, info, REQUEST_BIND_APPWIDGET);
1848             }
1849         }
1850     }
1851 
1852     /**
1853      * Creates and adds new folder to CellLayout
1854      */
addFolder(CellLayout layout, int container, final int screenId, int cellX, int cellY)1855     public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
1856             int cellY) {
1857         final FolderInfo folderInfo = new FolderInfo();
1858 
1859         // Update the model
1860         getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
1861 
1862         // Create the view
1863         FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout,
1864                 folderInfo);
1865         mWorkspace.addInScreen(newFolder, folderInfo);
1866         // Force measure the new folder icon
1867         CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
1868         parent.getShortcutsAndWidgets().measureChild(newFolder);
1869         return newFolder;
1870     }
1871 
1872     @Override
getFolderBoundingBox()1873     public Rect getFolderBoundingBox() {
1874         // We need to bound the folder to the currently visible workspace area
1875         return getWorkspace().getPageAreaRelativeToDragLayer();
1876     }
1877 
1878     @Override
updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height)1879     public void updateOpenFolderPosition(int[] inOutPosition, Rect bounds, int width, int height) {
1880         int left = inOutPosition[0];
1881         int top = inOutPosition[1];
1882         DeviceProfile grid = getDeviceProfile();
1883         int distFromEdgeOfScreen = getWorkspace().getPaddingLeft();
1884         if (grid.isPhone && (grid.availableWidthPx - width) < 4 * distFromEdgeOfScreen) {
1885             // Center the folder if it is very close to being centered anyway, by virtue of
1886             // filling the majority of the viewport. ie. remove it from the uncanny valley
1887             // of centeredness.
1888             left = (grid.availableWidthPx - width) / 2;
1889         } else if (width >= bounds.width()) {
1890             // If the folder doesn't fit within the bounds, center it about the desired bounds
1891             left = bounds.left + (bounds.width() - width) / 2;
1892         }
1893         if (height >= bounds.height()) {
1894             // Folder height is greater than page height, center on page
1895             top = bounds.top + (bounds.height() - height) / 2;
1896         } else {
1897             // Folder height is less than page height, so bound it to the absolute open folder
1898             // bounds if necessary
1899             Rect folderBounds = grid.getAbsoluteOpenFolderBounds();
1900             left = Math.max(folderBounds.left, Math.min(left, folderBounds.right - width));
1901             top = Math.max(folderBounds.top, Math.min(top, folderBounds.bottom - height));
1902         }
1903         inOutPosition[0] = left;
1904         inOutPosition[1] = top;
1905     }
1906 
1907     /**
1908      * Unbinds the view for the specified item, and removes the item and all its children.
1909      *
1910      * @param v the view being removed.
1911      * @param itemInfo the {@link ItemInfo} for this view.
1912      * @param deleteFromDb whether or not to delete this item from the db.
1913      */
removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb)1914     public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb) {
1915         if (itemInfo instanceof WorkspaceItemInfo) {
1916             // Remove the shortcut from the folder before removing it from launcher
1917             View folderIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
1918             if (folderIcon instanceof FolderIcon) {
1919                 ((FolderInfo) folderIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
1920             } else {
1921                 mWorkspace.removeWorkspaceItem(v);
1922             }
1923             if (deleteFromDb) {
1924                 getModelWriter().deleteItemFromDatabase(itemInfo);
1925             }
1926         } else if (itemInfo instanceof FolderInfo) {
1927             final FolderInfo folderInfo = (FolderInfo) itemInfo;
1928             if (v instanceof FolderIcon) {
1929                 ((FolderIcon) v).removeListeners();
1930             }
1931             mWorkspace.removeWorkspaceItem(v);
1932             if (deleteFromDb) {
1933                 getModelWriter().deleteFolderAndContentsFromDatabase(folderInfo);
1934             }
1935         } else if (itemInfo instanceof LauncherAppWidgetInfo) {
1936             final LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) itemInfo;
1937             mWorkspace.removeWorkspaceItem(v);
1938             if (deleteFromDb) {
1939                 getModelWriter().deleteWidgetInfo(widgetInfo, getAppWidgetHost());
1940             }
1941         } else {
1942             return false;
1943         }
1944         return true;
1945     }
1946 
1947     @Override
dispatchKeyEvent(KeyEvent event)1948     public boolean dispatchKeyEvent(KeyEvent event) {
1949         TestLogging.recordKeyEvent(TestProtocol.SEQUENCE_MAIN, "Key event", event);
1950         return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
1951     }
1952 
1953     @Override
dispatchTouchEvent(MotionEvent ev)1954     public boolean dispatchTouchEvent(MotionEvent ev) {
1955         switch (ev.getAction()) {
1956             case MotionEvent.ACTION_DOWN:
1957                 mTouchInProgress = true;
1958                 break;
1959             case MotionEvent.ACTION_UP:
1960                 mLastTouchUpTime = SystemClock.uptimeMillis();
1961                 // Follow through
1962             case MotionEvent.ACTION_CANCEL:
1963                 mTouchInProgress = false;
1964                 break;
1965         }
1966         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
1967         return super.dispatchTouchEvent(ev);
1968     }
1969 
1970     /**
1971      * Returns true if a touch interaction is in progress
1972      */
isTouchInProgress()1973     public boolean isTouchInProgress() {
1974         return mTouchInProgress;
1975     }
1976 
1977     @Override
onBackPressed()1978     public void onBackPressed() {
1979         if (finishAutoCancelActionMode()) {
1980             return;
1981         }
1982 
1983         if (mDragController.isDragging()) {
1984             mDragController.cancelDrag();
1985             return;
1986         }
1987 
1988         // Note: There should be at most one log per method call. This is enforced implicitly
1989         // by using if-else statements.
1990         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
1991         if (topView != null && topView.onBackPressed()) {
1992             // Handled by the floating view.
1993         } else {
1994             mStateManager.getState().onBackPressed(this);
1995         }
1996     }
1997 
onScreenOff()1998     protected void onScreenOff() {
1999         // Reset AllApps to its initial state only if we are not in the middle of
2000         // processing a multi-step drop
2001         if (mPendingRequestArgs == null) {
2002             if (!isInState(NORMAL)) {
2003                 onUiChangedWhileSleeping();
2004             }
2005             mStateManager.goToState(NORMAL);
2006         }
2007     }
2008 
2009     @TargetApi(Build.VERSION_CODES.M)
2010     @Override
onErrorStartingShortcut(Intent intent, ItemInfo info)2011     protected boolean onErrorStartingShortcut(Intent intent, ItemInfo info) {
2012         // Due to legacy reasons, direct call shortcuts require Launchers to have the
2013         // corresponding permission. Show the appropriate permission prompt if that
2014         // is the case.
2015         if (intent.getComponent() == null
2016                 && Intent.ACTION_CALL.equals(intent.getAction())
2017                 && checkSelfPermission(android.Manifest.permission.CALL_PHONE) !=
2018                 PackageManager.PERMISSION_GRANTED) {
2019 
2020             setWaitingForResult(PendingRequestArgs
2021                     .forIntent(REQUEST_PERMISSION_CALL_PHONE, intent, info));
2022             requestPermissions(new String[]{android.Manifest.permission.CALL_PHONE},
2023                     REQUEST_PERMISSION_CALL_PHONE);
2024             return true;
2025         } else {
2026             return false;
2027         }
2028     }
2029 
2030     @Override
startActivitySafely(View v, Intent intent, ItemInfo item)2031     public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
2032         if (!hasBeenResumed()) {
2033             // Workaround an issue where the WM launch animation is clobbered when finishing the
2034             // recents animation into launcher. Defer launching the activity until Launcher is
2035             // next resumed.
2036             addOnResumeCallback(() -> startActivitySafely(v, intent, item));
2037             if (mOnDeferredActivityLaunchCallback != null) {
2038                 mOnDeferredActivityLaunchCallback.run();
2039                 mOnDeferredActivityLaunchCallback = null;
2040             }
2041             return true;
2042         }
2043 
2044         boolean success = super.startActivitySafely(v, intent, item);
2045         if (success && v instanceof BubbleTextView) {
2046             // This is set to the view that launched the activity that navigated the user away
2047             // from launcher. Since there is no callback for when the activity has finished
2048             // launching, enable the press state and keep this reference to reset the press
2049             // state when we return to launcher.
2050             BubbleTextView btv = (BubbleTextView) v;
2051             btv.setStayPressed(true);
2052             addOnResumeCallback(() -> btv.setStayPressed(false));
2053         }
2054         return success;
2055     }
2056 
isHotseatLayout(View layout)2057     boolean isHotseatLayout(View layout) {
2058         // TODO: Remove this method
2059         return mHotseat != null && (layout == mHotseat);
2060     }
2061 
2062     /**
2063      * Returns the CellLayout of the specified container at the specified screen.
2064      */
getCellLayout(int container, int screenId)2065     public CellLayout getCellLayout(int container, int screenId) {
2066         return (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
2067                 ? mHotseat : mWorkspace.getScreenWithId(screenId);
2068     }
2069 
2070     @Override
onTrimMemory(int level)2071     public void onTrimMemory(int level) {
2072         super.onTrimMemory(level);
2073         if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
2074             // The widget preview db can result in holding onto over
2075             // 3MB of memory for caching which isn't necessary.
2076             SQLiteDatabase.releaseMemory();
2077 
2078             // This clears all widget bitmaps from the widget tray
2079             // TODO(hyunyoungs)
2080         }
2081     }
2082 
2083     @Override
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)2084     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2085         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
2086         final List<CharSequence> text = event.getText();
2087         text.clear();
2088         // Populate event with a fake title based on the current state.
2089         // TODO: When can workspace be null?
2090         text.add(mWorkspace == null
2091                 ? getString(R.string.home_screen)
2092                 : mStateManager.getState().getDescription(this));
2093         return result;
2094     }
2095 
2096     /**
2097      * Persistant callback which notifies when an activity launch is deferred because the activity
2098      * was not yet resumed.
2099      */
setOnDeferredActivityLaunchCallback(Runnable callback)2100     public void setOnDeferredActivityLaunchCallback(Runnable callback) {
2101         mOnDeferredActivityLaunchCallback = callback;
2102     }
2103 
2104     /**
2105      * Sets the next pages to bind synchronously on next bind.
2106      * @param pages should not be null.
2107      */
setPagesToBindSynchronously(@onNull IntSet pages)2108     public void setPagesToBindSynchronously(@NonNull IntSet pages) {
2109         mPagesToBindSynchronously = pages;
2110     }
2111 
2112     @Override
getPagesToBindSynchronously(IntArray orderedScreenIds)2113     public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
2114         IntSet visibleIds;
2115         if (!mPagesToBindSynchronously.isEmpty()) {
2116             visibleIds = mPagesToBindSynchronously;
2117         } else if (!mWorkspaceLoading) {
2118             visibleIds = mWorkspace.getCurrentPageScreenIds();
2119         } else {
2120             // If workspace binding is still in progress, getCurrentPageScreenIds won't be accurate,
2121             // and we should use mSynchronouslyBoundPages that's set during initial binding.
2122             visibleIds = mSynchronouslyBoundPages;
2123         }
2124         IntArray actualIds = new IntArray();
2125 
2126         IntSet result = new IntSet();
2127         if (visibleIds.isEmpty()) {
2128             if (TestProtocol.sDebugTracing) {
2129                 Log.d(TestProtocol.NULL_INT_SET, "getPagesToBindSynchronously (1): "
2130                         + result);
2131             }
2132             return result;
2133         }
2134         for (int id : orderedScreenIds.toArray()) {
2135             actualIds.add(id);
2136         }
2137         int firstId = visibleIds.getArray().get(0);
2138         int pairId = mWorkspace.getScreenPair(firstId);
2139         // Double check that actual screenIds contains the visibleId, as empty screens are hidden
2140         // in single panel.
2141         if (actualIds.contains(firstId)) {
2142             result.add(firstId);
2143             if (mDeviceProfile.isTwoPanels && actualIds.contains(pairId)) {
2144                 result.add(pairId);
2145             }
2146         } else if (LauncherAppState.getIDP(this).supportedProfiles.stream().anyMatch(
2147                 deviceProfile -> deviceProfile.isTwoPanels) && actualIds.contains(pairId)) {
2148             // Add the right panel if left panel is hidden when switching display, due to empty
2149             // pages being hidden in single panel.
2150             result.add(pairId);
2151         }
2152         if (TestProtocol.sDebugTracing) {
2153             Log.d(TestProtocol.NULL_INT_SET, "getPagesToBindSynchronously (2): "
2154                     + result);
2155         }
2156         return result;
2157     }
2158 
2159     /**
2160      * Clear any pending bind callbacks. This is called when is loader is planning to
2161      * perform a full rebind from scratch.
2162      */
2163     @Override
clearPendingBinds()2164     public void clearPendingBinds() {
2165         if (mPendingExecutor != null) {
2166             mPendingExecutor.cancel();
2167             mPendingExecutor = null;
2168 
2169             // We might have set this flag previously and forgot to clear it.
2170             mAppsView.getAppsStore()
2171                     .disableDeferUpdatesSilently(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
2172         }
2173     }
2174 
2175     /**
2176      * Refreshes the shortcuts shown on the workspace.
2177      *
2178      * Implementation of the method from LauncherModel.Callbacks.
2179      */
startBinding()2180     public void startBinding() {
2181         Object traceToken = TraceHelper.INSTANCE.beginSection("startBinding");
2182         // Floating panels (except the full widget sheet) are associated with individual icons. If
2183         // we are starting a fresh bind, close all such panels as all the icons are about
2184         // to go away.
2185         AbstractFloatingView.closeOpenViews(this, true, TYPE_ALL & ~TYPE_REBIND_SAFE);
2186 
2187         setWorkspaceLoading(true);
2188 
2189         // Clear the workspace because it's going to be rebound
2190         mDragController.cancelDrag();
2191 
2192         mWorkspace.clearDropTargets();
2193         mWorkspace.removeAllWorkspaceScreens();
2194         mAppWidgetHost.clearViews();
2195 
2196         if (mHotseat != null) {
2197             mHotseat.resetLayout(getDeviceProfile().isVerticalBarLayout());
2198         }
2199         TraceHelper.INSTANCE.endSection(traceToken);
2200     }
2201 
2202     @Override
bindScreens(IntArray orderedScreenIds)2203     public void bindScreens(IntArray orderedScreenIds) {
2204         int firstScreenPosition = 0;
2205         if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
2206                 orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != firstScreenPosition) {
2207             orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
2208             orderedScreenIds.add(firstScreenPosition, Workspace.FIRST_SCREEN_ID);
2209         } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
2210             // If there are no screens, we need to have an empty screen
2211             mWorkspace.addExtraEmptyScreens();
2212         }
2213         bindAddScreens(orderedScreenIds);
2214 
2215         // After we have added all the screens, if the wallpaper was locked to the default state,
2216         // then notify to indicate that it can be released and a proper wallpaper offset can be
2217         // computed before the next layout
2218         mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
2219     }
2220 
bindAddScreens(IntArray orderedScreenIds)2221     private void bindAddScreens(IntArray orderedScreenIds) {
2222         if (mDeviceProfile.isTwoPanels) {
2223             // Some empty pages might have been removed while the phone was in a single panel
2224             // mode, so we want to add those empty pages back.
2225             IntSet screenIds = IntSet.wrap(orderedScreenIds);
2226             orderedScreenIds.forEach(screenId -> screenIds.add(mWorkspace.getScreenPair(screenId)));
2227             orderedScreenIds = screenIds.getArray();
2228         }
2229 
2230         int count = orderedScreenIds.size();
2231         for (int i = 0; i < count; i++) {
2232             int screenId = orderedScreenIds.get(i);
2233             if (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID) {
2234                 // No need to bind the first screen, as its always bound.
2235                 continue;
2236             }
2237             mWorkspace.insertNewWorkspaceScreenBeforeEmptyScreen(screenId);
2238         }
2239     }
2240 
2241     @Override
preAddApps()2242     public void preAddApps() {
2243         // If there's an undo snackbar, force it to complete to ensure empty screens are removed
2244         // before trying to add new items.
2245         mModelWriter.commitDelete();
2246         AbstractFloatingView snackbar = AbstractFloatingView.getOpenView(this, TYPE_SNACKBAR);
2247         if (snackbar != null) {
2248             snackbar.post(() -> snackbar.close(true));
2249         }
2250     }
2251 
2252     @Override
bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated)2253     public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
2254             ArrayList<ItemInfo> addAnimated) {
2255         // Add the new screens
2256         if (newScreens != null) {
2257             // newScreens can contain an empty right panel that is already bound, but not known
2258             // by BgDataModel.
2259             newScreens.removeAllValues(mWorkspace.mScreenOrder);
2260             bindAddScreens(newScreens);
2261         }
2262 
2263         // We add the items without animation on non-visible pages, and with
2264         // animations on the new page (which we will try and snap to).
2265         if (addNotAnimated != null && !addNotAnimated.isEmpty()) {
2266             bindItems(addNotAnimated, false);
2267         }
2268         if (addAnimated != null && !addAnimated.isEmpty()) {
2269             bindItems(addAnimated, true);
2270         }
2271 
2272         // Remove the extra empty screen
2273         mWorkspace.removeExtraEmptyScreen(false);
2274     }
2275 
2276     /**
2277      * Bind the items start-end from the list.
2278      *
2279      * Implementation of the method from LauncherModel.Callbacks.
2280      */
2281     @Override
bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons)2282     public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
2283         bindItems(items, forceAnimateIcons, /* focusFirstItemForAccessibility= */ false);
2284     }
2285 
2286 
2287     /**
2288      * Bind the items start-end from the list.
2289      *
2290      * Implementation of the method from LauncherModel.Callbacks.
2291      *
2292      * @param focusFirstItemForAccessibility true iff the first item to be added to the workspace
2293      *                                       should be focused for accessibility.
2294      */
bindItems( final List<ItemInfo> items, final boolean forceAnimateIcons, final boolean focusFirstItemForAccessibility)2295     public void bindItems(
2296             final List<ItemInfo> items,
2297             final boolean forceAnimateIcons,
2298             final boolean focusFirstItemForAccessibility) {
2299         // Get the list of added items and intersect them with the set of items here
2300         final Collection<Animator> bounceAnims = new ArrayList<>();
2301         boolean canAnimatePageChange = canAnimatePageChange();
2302         Workspace workspace = mWorkspace;
2303         int newItemsScreenId = -1;
2304         int end = items.size();
2305         View newView = null;
2306         for (int i = 0; i < end; i++) {
2307             final ItemInfo item = items.get(i);
2308 
2309             // Short circuit if we are loading dock items for a configuration which has no dock
2310             if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
2311                     mHotseat == null) {
2312                 continue;
2313             }
2314 
2315             final View view;
2316             switch (item.itemType) {
2317                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
2318                 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
2319                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
2320                     WorkspaceItemInfo info = (WorkspaceItemInfo) item;
2321                     view = createShortcut(info);
2322                     break;
2323                 }
2324                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
2325                     view = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this,
2326                             (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
2327                             (FolderInfo) item);
2328                     break;
2329                 }
2330                 case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
2331                 case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
2332                     view = inflateAppWidget((LauncherAppWidgetInfo) item);
2333                     if (view == null) {
2334                         continue;
2335                     }
2336                     break;
2337                 }
2338                 default:
2339                     throw new RuntimeException("Invalid Item Type");
2340             }
2341 
2342             /*
2343              * Remove colliding items.
2344              */
2345             if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
2346                 CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
2347                 if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
2348                     View v = cl.getChildAt(item.cellX, item.cellY);
2349                     Object tag = v.getTag();
2350                     String desc = "Collision while binding workspace item: " + item
2351                             + ". Collides with " + tag;
2352                     if (FeatureFlags.IS_STUDIO_BUILD) {
2353                         throw (new RuntimeException(desc));
2354                     } else {
2355                         Log.d(TAG, desc);
2356                         getModelWriter().deleteItemFromDatabase(item);
2357                         continue;
2358                     }
2359                 }
2360             }
2361             workspace.addInScreenFromBind(view, item);
2362             if (forceAnimateIcons) {
2363                 // Animate all the applications up now
2364                 view.setAlpha(0f);
2365                 view.setScaleX(0f);
2366                 view.setScaleY(0f);
2367                 bounceAnims.add(createNewAppBounceAnimation(view, i));
2368                 newItemsScreenId = item.screenId;
2369             }
2370 
2371             if (newView == null) {
2372                 newView = view;
2373             }
2374         }
2375 
2376         View viewToFocus = newView;
2377         // Animate to the correct pager
2378         if (forceAnimateIcons && newItemsScreenId > -1) {
2379             AnimatorSet anim = new AnimatorSet();
2380             anim.playTogether(bounceAnims);
2381             if (focusFirstItemForAccessibility && viewToFocus != null) {
2382                 anim.addListener(new AnimatorListenerAdapter() {
2383                     @Override
2384                     public void onAnimationEnd(Animator animation) {
2385                         viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
2386                     }
2387                 });
2388             }
2389 
2390             int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
2391             final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
2392             final Runnable startBounceAnimRunnable = anim::start;
2393 
2394             if (canAnimatePageChange && newItemsScreenId != currentScreenId) {
2395                 // We post the animation slightly delayed to prevent slowdowns
2396                 // when we are loading right after we return to launcher.
2397                 mWorkspace.postDelayed(new Runnable() {
2398                     public void run() {
2399                         if (mWorkspace != null) {
2400                             closeOpenViews(false);
2401 
2402                             mWorkspace.snapToPage(newScreenIndex);
2403                             mWorkspace.postDelayed(startBounceAnimRunnable,
2404                                     NEW_APPS_ANIMATION_DELAY);
2405                         }
2406                     }
2407                 }, NEW_APPS_PAGE_MOVE_DELAY);
2408             } else {
2409                 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
2410             }
2411         } else if (focusFirstItemForAccessibility && viewToFocus != null) {
2412             viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
2413         }
2414         workspace.requestLayout();
2415     }
2416 
2417     /**
2418      * Add the views for a widget to the workspace.
2419      */
bindAppWidget(LauncherAppWidgetInfo item)2420     public void bindAppWidget(LauncherAppWidgetInfo item) {
2421         View view = inflateAppWidget(item);
2422         if (view != null) {
2423             mWorkspace.addInScreen(view, item);
2424             mWorkspace.requestLayout();
2425         }
2426     }
2427 
inflateAppWidget(LauncherAppWidgetInfo item)2428     private View inflateAppWidget(LauncherAppWidgetInfo item) {
2429         if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) {
2430             item.providerName = QsbContainerView.getSearchComponentName(this);
2431             if (item.providerName == null) {
2432                 getModelWriter().deleteItemFromDatabase(item);
2433                 return null;
2434             }
2435         }
2436         final AppWidgetHostView view;
2437         if (mIsSafeModeEnabled) {
2438             view = new PendingAppWidgetHostView(this, item, mIconCache, true);
2439             prepareAppWidget(view, item);
2440             return view;
2441         }
2442 
2443         Object traceToken = TraceHelper.INSTANCE.beginSection("BIND_WIDGET_id=" + item.appWidgetId);
2444 
2445         try {
2446             final LauncherAppWidgetProviderInfo appWidgetInfo;
2447             String removalReason = "";
2448 
2449             if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
2450                 // If the provider is not ready, bind as a pending widget.
2451                 appWidgetInfo = null;
2452                 removalReason = "the provider isn't ready.";
2453             } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
2454                 // The widget id is not valid. Try to find the widget based on the provider info.
2455                 appWidgetInfo = mAppWidgetManager.findProvider(item.providerName, item.user);
2456                 if (appWidgetInfo == null) {
2457                     if (WidgetsModel.GO_DISABLE_WIDGETS) {
2458                         removalReason = "widgets are disabled on go device.";
2459                     } else {
2460                         removalReason =
2461                                 "WidgetManagerHelper cannot find a provider from provider info.";
2462                     }
2463                 }
2464             } else {
2465                 appWidgetInfo = mAppWidgetManager.getLauncherAppWidgetInfo(item.appWidgetId);
2466                 if (appWidgetInfo == null) {
2467                     if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) {
2468                         removalReason =
2469                                 "CustomWidgetManager cannot find provider from that widget id.";
2470                     } else {
2471                         removalReason = "AppWidgetManager cannot find provider for that widget id."
2472                                 + " It could be because AppWidgetService is not available, or the"
2473                                 + " appWidgetId has not been bound to a the provider yet, or you"
2474                                 + " don't have access to that appWidgetId.";
2475                     }
2476                 }
2477             }
2478 
2479             // If the provider is ready, but the width is not yet restored, try to restore it.
2480             if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
2481                     && (item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED)) {
2482                 if (appWidgetInfo == null) {
2483                     FileLog.d(TAG, "Removing restored widget: id=" + item.appWidgetId
2484                             + " belongs to component " + item.providerName + " user " + item.user
2485                             + ", as the provider is null and " + removalReason);
2486                     getModelWriter().deleteItemFromDatabase(item);
2487                     return null;
2488                 }
2489 
2490                 // If we do not have a valid id, try to bind an id.
2491                 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
2492                     if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) {
2493                         // Id has not been allocated yet. Allocate a new id.
2494                         item.appWidgetId = mAppWidgetHost.allocateAppWidgetId();
2495                         item.restoreStatus |= LauncherAppWidgetInfo.FLAG_ID_ALLOCATED;
2496 
2497                         // Also try to bind the widget. If the bind fails, the user will be shown
2498                         // a click to setup UI, which will ask for the bind permission.
2499                         PendingAddWidgetInfo pendingInfo =
2500                                 new PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer);
2501                         pendingInfo.spanX = item.spanX;
2502                         pendingInfo.spanY = item.spanY;
2503                         pendingInfo.minSpanX = item.minSpanX;
2504                         pendingInfo.minSpanY = item.minSpanY;
2505                         Bundle options = pendingInfo.getDefaultSizeOptions(this);
2506 
2507                         boolean isDirectConfig =
2508                                 item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG);
2509                         if (isDirectConfig && item.bindOptions != null) {
2510                             Bundle newOptions = item.bindOptions.getExtras();
2511                             if (options != null) {
2512                                 newOptions.putAll(options);
2513                             }
2514                             options = newOptions;
2515                         }
2516                         boolean success = mAppWidgetManager.bindAppWidgetIdIfAllowed(
2517                                 item.appWidgetId, appWidgetInfo, options);
2518 
2519                         // We tried to bind once. If we were not able to bind, we would need to
2520                         // go through the permission dialog, which means we cannot skip the config
2521                         // activity.
2522                         item.bindOptions = null;
2523                         item.restoreStatus &= ~LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG;
2524 
2525                         // Bind succeeded
2526                         if (success) {
2527                             // If the widget has a configure activity, it is still needs to set it
2528                             // up, otherwise the widget is ready to go.
2529                             item.restoreStatus = (appWidgetInfo.configure == null) || isDirectConfig
2530                                     ? LauncherAppWidgetInfo.RESTORE_COMPLETED
2531                                     : LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
2532                         }
2533 
2534                         getModelWriter().updateItemInDatabase(item);
2535                     }
2536                 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
2537                         && (appWidgetInfo.configure == null)) {
2538                     // The widget was marked as UI not ready, but there is no configure activity to
2539                     // update the UI.
2540                     item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2541                     getModelWriter().updateItemInDatabase(item);
2542                 }
2543                 else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY)
2544                         && appWidgetInfo.configure != null) {
2545                     if (mAppWidgetManager.isAppWidgetRestored(item.appWidgetId)) {
2546                         item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED;
2547                         getModelWriter().updateItemInDatabase(item);
2548                     }
2549                 }
2550             }
2551 
2552             if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
2553                 // Verify that we own the widget
2554                 if (appWidgetInfo == null) {
2555                     FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
2556                     getModelWriter().deleteWidgetInfo(item, getAppWidgetHost());
2557                     return null;
2558                 }
2559 
2560                 item.minSpanX = appWidgetInfo.minSpanX;
2561                 item.minSpanY = appWidgetInfo.minSpanY;
2562                 view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
2563             } else if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
2564                     && appWidgetInfo != null) {
2565                 mAppWidgetHost.addPendingView(item.appWidgetId,
2566                         new PendingAppWidgetHostView(this, item, mIconCache, false));
2567                 view = mAppWidgetHost.createView(this, item.appWidgetId, appWidgetInfo);
2568             } else {
2569                 view = new PendingAppWidgetHostView(this, item, mIconCache, false);
2570             }
2571             prepareAppWidget(view, item);
2572         } finally {
2573             TraceHelper.INSTANCE.endSection(traceToken);
2574         }
2575 
2576         return view;
2577     }
2578 
2579     /**
2580      * Restores a pending widget.
2581      *
2582      * @param appWidgetId The app widget id
2583      */
completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag)2584     private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
2585         LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
2586         if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
2587             Log.e(TAG, "Widget update called, when the widget no longer exists.");
2588             return null;
2589         }
2590 
2591         LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) view.getTag();
2592         info.restoreStatus = finalRestoreFlag;
2593         if (info.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
2594             info.pendingItemInfo = null;
2595         }
2596 
2597         if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) {
2598             view.reInflate();
2599         }
2600 
2601         getModelWriter().updateItemInDatabase(info);
2602         return info;
2603     }
2604 
clearPendingExecutor(ViewOnDrawExecutor executor)2605     public void clearPendingExecutor(ViewOnDrawExecutor executor) {
2606         if (mPendingExecutor == executor) {
2607             mPendingExecutor = null;
2608         }
2609     }
2610 
2611     @Override
2612     @TargetApi(Build.VERSION_CODES.S)
onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks)2613     public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
2614         mSynchronouslyBoundPages = boundPages;
2615         mPagesToBindSynchronously = new IntSet();
2616 
2617         clearPendingBinds();
2618         ViewOnDrawExecutor executor = new ViewOnDrawExecutor(pendingTasks);
2619         mPendingExecutor = executor;
2620         if (!isInState(ALL_APPS)) {
2621             mAppsView.getAppsStore().enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW);
2622             pendingTasks.add(() -> mAppsView.getAppsStore().disableDeferUpdates(
2623                     AllAppsStore.DEFER_UPDATES_NEXT_DRAW));
2624         }
2625 
2626         AlphaProperty property = mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD);
2627         if (property.getValue() < 1) {
2628             ObjectAnimator anim = ObjectAnimator.ofFloat(property, MultiValueAlpha.VALUE, 1);
2629             anim.addListener(AnimatorListeners.forEndCallback(executor::onLoadAnimationCompleted));
2630             anim.start();
2631         } else {
2632             executor.onLoadAnimationCompleted();
2633         }
2634         executor.attachTo(this);
2635         if (Utilities.ATLEAST_S) {
2636             Trace.endAsyncSection(DISPLAY_WORKSPACE_TRACE_METHOD_NAME,
2637                     DISPLAY_WORKSPACE_TRACE_COOKIE);
2638         }
2639     }
2640 
2641     /**
2642      * Callback saying that there aren't any more items to bind.
2643      *
2644      * Implementation of the method from LauncherModel.Callbacks.
2645      */
finishBindingItems(IntSet pagesBoundFirst)2646     public void finishBindingItems(IntSet pagesBoundFirst) {
2647         Object traceToken = TraceHelper.INSTANCE.beginSection("finishBindingItems");
2648         mWorkspace.restoreInstanceStateForRemainingPages();
2649 
2650         setWorkspaceLoading(false);
2651 
2652         if (mPendingActivityResult != null) {
2653             handleActivityResult(mPendingActivityResult.requestCode,
2654                     mPendingActivityResult.resultCode, mPendingActivityResult.data);
2655             mPendingActivityResult = null;
2656         }
2657 
2658         int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty()
2659                 ? mWorkspace.getPageIndexForScreenId(pagesBoundFirst.getArray().get(0))
2660                 : PagedView.INVALID_PAGE;
2661         // When undoing the removal of the last item on a page, return to that page.
2662         // Since we are just resetting the current page without user interaction,
2663         // override the previous page so we don't log the page switch.
2664         mWorkspace.setCurrentPage(currentPage, currentPage /* overridePrevPage */);
2665         mPagesToBindSynchronously = new IntSet();
2666 
2667         // Cache one page worth of icons
2668         getViewCache().setCacheSize(R.layout.folder_application,
2669                 mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows);
2670         getViewCache().setCacheSize(R.layout.folder_page, 2);
2671 
2672         TraceHelper.INSTANCE.endSection(traceToken);
2673     }
2674 
canAnimatePageChange()2675     private boolean canAnimatePageChange() {
2676         if (mDragController.isDragging()) {
2677             return false;
2678         } else {
2679             return (SystemClock.uptimeMillis() - mLastTouchUpTime)
2680                     > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
2681         }
2682     }
2683 
2684     /**
2685      * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
2686      * animation.
2687      *
2688      * @param preferredItemId The id of the preferred item to match to if it exists.
2689      * @param packageName The package name of the app to match.
2690      * @param user The user of the app to match.
2691      */
getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user)2692     public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
2693         final ItemInfoMatcher preferredItem = (info, cn) ->
2694                 info != null && info.id == preferredItemId;
2695         final ItemInfoMatcher packageAndUserAndApp = (info, cn) ->
2696                 info != null
2697                         && info.itemType == ITEM_TYPE_APPLICATION
2698                         && info.user.equals(user)
2699                         && info.getTargetComponent() != null
2700                         && TextUtils.equals(info.getTargetComponent().getPackageName(),
2701                         packageName);
2702 
2703         if (isInState(LauncherState.ALL_APPS)) {
2704             return getFirstMatch(Collections.singletonList(mAppsView.getActiveRecyclerView()),
2705                     preferredItem, packageAndUserAndApp);
2706         } else {
2707             List<ViewGroup> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
2708             containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets());
2709             mWorkspace.forEachVisiblePage(page
2710                     -> containers.add(((CellLayout) page).getShortcutsAndWidgets()));
2711 
2712             // Order: Preferred item by itself or in folder, then by matching package/user
2713             if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
2714                 return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem),
2715                         packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
2716             } else {
2717                 // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
2718                 // FolderAdaptiveIcon as the background.
2719                 return getFirstMatch(containers, preferredItem, packageAndUserAndApp);
2720             }
2721         }
2722     }
2723 
2724     /**
2725      * Finds the first view matching the ordered operators across the given viewgroups in order.
2726      * @param containers List of ViewGroups to scan, in order of preference.
2727      * @param operators List of operators, in order starting from best matching operator.
2728      */
getFirstMatch(Iterable<ViewGroup> containers, final ItemInfoMatcher... operators)2729     private static View getFirstMatch(Iterable<ViewGroup> containers,
2730             final ItemInfoMatcher... operators) {
2731         for (ItemInfoMatcher operator : operators) {
2732             for (ViewGroup container : containers) {
2733                 View match = mapOverViewGroup(container, operator);
2734                 if (match != null) {
2735                     return match;
2736                 }
2737             }
2738         }
2739         return null;
2740     }
2741 
2742     /**
2743      * Returns the first view matching the operator in the given ViewGroups, or null if none.
2744      * Forward iteration matters.
2745      */
mapOverViewGroup(ViewGroup container, ItemInfoMatcher op)2746     private static View mapOverViewGroup(ViewGroup container, ItemInfoMatcher op) {
2747         final int itemCount = container.getChildCount();
2748         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
2749             View item = container.getChildAt(itemIdx);
2750             if (op.matchesInfo((ItemInfo) item.getTag())) {
2751                 return item;
2752             }
2753         }
2754         return null;
2755     }
2756 
createNewAppBounceAnimation(View v, int i)2757     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
2758         ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
2759                 .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
2760         bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY);
2761         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
2762         return bounceAnim;
2763     }
2764 
announceForAccessibility(@tringRes int stringResId)2765     private void announceForAccessibility(@StringRes int stringResId) {
2766         getDragLayer().announceForAccessibility(getString(stringResId));
2767     }
2768 
2769     /**
2770      * Add the icons for all apps.
2771      *
2772      * Implementation of the method from LauncherModel.Callbacks.
2773      */
2774     @Override
2775     @TargetApi(Build.VERSION_CODES.S)
bindAllApplications(AppInfo[] apps, int flags)2776     public void bindAllApplications(AppInfo[] apps, int flags) {
2777         mAppsView.getAppsStore().setApps(apps, flags);
2778         PopupContainerWithArrow.dismissInvalidPopup(this);
2779         if (Utilities.ATLEAST_S) {
2780             Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
2781                     DISPLAY_ALL_APPS_TRACE_COOKIE);
2782         }
2783     }
2784 
2785     /**
2786      * Copies LauncherModel's map of activities to shortcut counts to Launcher's. This is necessary
2787      * because LauncherModel's map is updated in the background, while Launcher runs on the UI.
2788      */
2789     @Override
bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy)2790     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
2791         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
2792     }
2793 
2794     @Override
bindIncrementalDownloadProgressUpdated(AppInfo app)2795     public void bindIncrementalDownloadProgressUpdated(AppInfo app) {
2796         mAppsView.getAppsStore().updateProgressBar(app);
2797     }
2798 
2799     @Override
bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets)2800     public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
2801         mWorkspace.widgetsRestored(widgets);
2802     }
2803 
2804     /**
2805      * Some shortcuts were updated in the background.
2806      * Implementation of the method from LauncherModel.Callbacks.
2807      *
2808      * @param updated list of shortcuts which have changed.
2809      */
2810     @Override
bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated)2811     public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
2812         if (!updated.isEmpty()) {
2813             mWorkspace.updateWorkspaceItems(updated, this);
2814             PopupContainerWithArrow.dismissInvalidPopup(this);
2815         }
2816     }
2817 
2818     /**
2819      * Update the state of a package, typically related to install state.
2820      *
2821      * Implementation of the method from LauncherModel.Callbacks.
2822      */
2823     @Override
bindRestoreItemsChange(HashSet<ItemInfo> updates)2824     public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
2825         mWorkspace.updateRestoreItems(updates, this);
2826     }
2827 
2828     /**
2829      * A package was uninstalled/updated.  We take both the super set of packageNames
2830      * in addition to specific applications to remove, the reason being that
2831      * this can be called when a package is updated as well.  In that scenario,
2832      * we only remove specific components from the workspace and hotseat, where as
2833      * package-removal should clear all items by package name.
2834      */
2835     @Override
bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher)2836     public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) {
2837         mWorkspace.removeItemsByMatcher(matcher);
2838         mDragController.onAppsRemoved(matcher);
2839         PopupContainerWithArrow.dismissInvalidPopup(this);
2840     }
2841 
2842     @Override
bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets)2843     public void bindAllWidgets(final List<WidgetsListBaseEntry> allWidgets) {
2844         mPopupDataProvider.setAllWidgets(allWidgets);
2845     }
2846 
2847     /**
2848      * @param packageUser if null, refreshes all widgets and shortcuts, otherwise only
2849      *                    refreshes the widgets and shortcuts associated with the given package/user
2850      */
refreshAndBindWidgetsForPackageUser(@ullable PackageUserKey packageUser)2851     public void refreshAndBindWidgetsForPackageUser(@Nullable PackageUserKey packageUser) {
2852         mModel.refreshAndBindWidgetsAndShortcuts(packageUser);
2853     }
2854 
2855     /**
2856      * $ adb shell dumpsys activity com.android.launcher3.Launcher [--all]
2857      */
2858     @Override
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)2859     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
2860         super.dump(prefix, fd, writer, args);
2861 
2862         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
2863             writer.println(prefix + "Workspace Items");
2864             for (int i = 0; i < mWorkspace.getPageCount(); i++) {
2865                 writer.println(prefix + "  Homescreen " + i);
2866 
2867                 ViewGroup layout = ((CellLayout) mWorkspace.getPageAt(i)).getShortcutsAndWidgets();
2868                 for (int j = 0; j < layout.getChildCount(); j++) {
2869                     Object tag = layout.getChildAt(j).getTag();
2870                     if (tag != null) {
2871                         writer.println(prefix + "    " + tag.toString());
2872                     }
2873                 }
2874             }
2875 
2876             writer.println(prefix + "  Hotseat");
2877             ViewGroup layout = mHotseat.getShortcutsAndWidgets();
2878             for (int j = 0; j < layout.getChildCount(); j++) {
2879                 Object tag = layout.getChildAt(j).getTag();
2880                 if (tag != null) {
2881                     writer.println(prefix + "    " + tag.toString());
2882                 }
2883             }
2884         }
2885 
2886         writer.println(prefix + "Misc:");
2887         dumpMisc(prefix + "\t", writer);
2888         writer.println(prefix + "\tmWorkspaceLoading=" + mWorkspaceLoading);
2889         writer.println(prefix + "\tmPendingRequestArgs=" + mPendingRequestArgs
2890                 + " mPendingActivityResult=" + mPendingActivityResult);
2891         writer.println(prefix + "\tmRotationHelper: " + mRotationHelper);
2892         writer.println(prefix + "\tmAppWidgetHost.isListening: " + mAppWidgetHost.isListening());
2893 
2894         // Extra logging for general debugging
2895         mDragLayer.dump(prefix, writer);
2896         mStateManager.dump(prefix, writer);
2897         mPopupDataProvider.dump(prefix, writer);
2898         mDeviceProfile.dump(prefix, writer);
2899 
2900         try {
2901             FileLog.flushAll(writer);
2902         } catch (Exception e) {
2903             // Ignore
2904         }
2905 
2906         mModel.dumpState(prefix, fd, writer, args);
2907 
2908         if (mLauncherCallbacks != null) {
2909             mLauncherCallbacks.dump(prefix, fd, writer, args);
2910         }
2911         mOverlayManager.dump(prefix, writer);
2912     }
2913 
2914     @Override
onProvideKeyboardShortcuts( List<KeyboardShortcutGroup> data, Menu menu, int deviceId)2915     public void onProvideKeyboardShortcuts(
2916             List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
2917 
2918         ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>();
2919         if (isInState(NORMAL)) {
2920             shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.all_apps_button_label),
2921                     KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON));
2922             shortcutInfos.add(new KeyboardShortcutInfo(getString(R.string.widget_button_text),
2923                     KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON));
2924         }
2925         getSupportedActions(this,  getCurrentFocus()).forEach(la ->
2926                 shortcutInfos.add(new KeyboardShortcutInfo(
2927                         la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON)));
2928         if (!shortcutInfos.isEmpty()) {
2929             data.add(new KeyboardShortcutGroup(getString(R.string.home_screen), shortcutInfos));
2930         }
2931 
2932         super.onProvideKeyboardShortcuts(data, menu, deviceId);
2933     }
2934 
2935     @Override
onKeyShortcut(int keyCode, KeyEvent event)2936     public boolean onKeyShortcut(int keyCode, KeyEvent event) {
2937         if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
2938             switch (keyCode) {
2939                 case KeyEvent.KEYCODE_A:
2940                     if (isInState(NORMAL)) {
2941                         getStateManager().goToState(ALL_APPS);
2942                         return true;
2943                     }
2944                     break;
2945                 case KeyEvent.KEYCODE_W:
2946                     if (isInState(NORMAL)) {
2947                         OptionsPopupView.openWidgets(this);
2948                         return true;
2949                     }
2950                     break;
2951                 default:
2952                     for (LauncherAction la : getSupportedActions(this, getCurrentFocus())) {
2953                         if (la.keyCode == keyCode) {
2954                             return la.invokeFromKeyboard(getCurrentFocus());
2955                         }
2956                     }
2957             }
2958         }
2959         return super.onKeyShortcut(keyCode, event);
2960     }
2961 
2962     @Override
onKeyUp(int keyCode, KeyEvent event)2963     public boolean onKeyUp(int keyCode, KeyEvent event) {
2964         if (keyCode == KeyEvent.KEYCODE_MENU) {
2965             // KEYCODE_MENU is sent by some tests, for example
2966             // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling.
2967             if (!mDragController.isDragging() && !mWorkspace.isSwitchingState() &&
2968                     isInState(NORMAL)) {
2969                 // Close any open floating views.
2970                 closeOpenViews();
2971 
2972                 // Setting the touch point to (-1, -1) will show the options popup in the center of
2973                 // the screen.
2974                 if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
2975                     Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
2976                 }
2977                 showDefaultOptions(-1, -1);
2978             }
2979             return true;
2980         }
2981         return super.onKeyUp(keyCode, event);
2982     }
2983 
2984     /**
2985      * Shows the default options popup
2986      */
showDefaultOptions(float x, float y)2987     public void showDefaultOptions(float x, float y) {
2988         OptionsPopupView.show(this, getPopupTarget(x, y), OptionsPopupView.getOptions(this),
2989                 false);
2990     }
2991 
2992     /**
2993      * Returns target rectangle for anchoring a popup menu.
2994      */
getPopupTarget(float x, float y)2995     protected RectF getPopupTarget(float x, float y) {
2996         float halfSize = getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
2997         if (x < 0 || y < 0) {
2998             x = mDragLayer.getWidth() / 2;
2999             y = mDragLayer.getHeight() / 2;
3000         }
3001         return new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
3002     }
3003 
3004     @Override
shouldUseColorExtractionForPopup()3005     public boolean shouldUseColorExtractionForPopup() {
3006         return getTopOpenViewWithType(this, TYPE_FOLDER) == null
3007                 && getStateManager().getState() != LauncherState.ALL_APPS;
3008     }
3009 
3010     @Override
collectStateHandlers(List<StateHandler> out)3011     protected void collectStateHandlers(List<StateHandler> out) {
3012         out.add(getAllAppsController());
3013         out.add(getWorkspace());
3014     }
3015 
createTouchControllers()3016     public TouchController[] createTouchControllers() {
3017         return new TouchController[] {getDragController(), new AllAppsSwipeController(this)};
3018     }
3019 
useFadeOutAnimationForLauncherStart(CancellationSignal signal)3020     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) { }
3021 
onDragLayerHierarchyChanged()3022     public void onDragLayerHierarchyChanged() { }
3023 
3024     @Override
returnToHomescreen()3025     public void returnToHomescreen() {
3026         super.returnToHomescreen();
3027         getStateManager().goToState(LauncherState.NORMAL);
3028     }
3029 
closeOpenViews()3030     private void closeOpenViews() {
3031         closeOpenViews(true);
3032     }
3033 
closeOpenViews(boolean animate)3034     protected void closeOpenViews(boolean animate) {
3035         AbstractFloatingView.closeAllOpenViews(this, animate);
3036     }
3037 
getSupportedShortcuts()3038     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
3039         return Stream.of(APP_INFO, WIDGETS, INSTALL);
3040     }
3041 
createAccessibilityDelegate()3042     protected LauncherAccessibilityDelegate createAccessibilityDelegate() {
3043         return new LauncherAccessibilityDelegate(this);
3044     }
3045 
3046     /**
3047      * @see LauncherState#getOverviewScaleAndOffset(Launcher)
3048      */
getNormalOverviewScaleAndOffset()3049     public float[] getNormalOverviewScaleAndOffset() {
3050         return new float[] {NO_SCALE, NO_OFFSET};
3051     }
3052 
getLauncher(Context context)3053     public static Launcher getLauncher(Context context) {
3054         return fromContext(context);
3055     }
3056 
3057     /**
3058      * Just a wrapper around the type cast to allow easier tracking of calls.
3059      */
cast(ActivityContext activityContext)3060     public static <T extends Launcher> T cast(ActivityContext activityContext) {
3061         return (T) activityContext;
3062     }
3063 
3064     /**
3065      * Cross-fades the launcher's updated appearance with its previous appearance.
3066      *
3067      * This method is used to cross-fade UI updates on activity creation, specifically dark mode
3068      * updates.
3069      */
crossFadeWithPreviousAppearance()3070     private void crossFadeWithPreviousAppearance() {
3071         NonConfigInstance lastInstance = (NonConfigInstance) getLastNonConfigurationInstance();
3072 
3073         if (lastInstance == null || lastInstance.snapshot == null) {
3074             return;
3075         }
3076 
3077         ImageView crossFadeHelper = new ImageView(this);
3078         crossFadeHelper.setImageBitmap(lastInstance.snapshot);
3079         crossFadeHelper.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
3080 
3081         InsettableFrameLayout.LayoutParams layoutParams = new InsettableFrameLayout.LayoutParams(
3082                 InsettableFrameLayout.LayoutParams.MATCH_PARENT,
3083                 InsettableFrameLayout.LayoutParams.MATCH_PARENT);
3084 
3085         layoutParams.ignoreInsets = true;
3086 
3087         crossFadeHelper.setLayoutParams(layoutParams);
3088 
3089         getRootView().addView(crossFadeHelper);
3090 
3091         crossFadeHelper
3092                 .animate()
3093                 .setDuration(THEME_CROSS_FADE_ANIMATION_DURATION)
3094                 .alpha(0f)
3095                 .withEndAction(() -> getRootView().removeView(crossFadeHelper))
3096                 .start();
3097     }
3098 
supportsAdaptiveIconAnimation(View clickedView)3099     public boolean supportsAdaptiveIconAnimation(View clickedView) {
3100         return false;
3101     }
3102 
getDefaultWorkspaceDragOptions()3103     public DragOptions getDefaultWorkspaceDragOptions() {
3104         return new DragOptions();
3105     }
3106 
3107     private static class NonConfigInstance {
3108         public Configuration config;
3109         public Bitmap snapshot;
3110     }
3111 
3112     @Override
getStatsLogManager()3113     public StatsLogManager getStatsLogManager() {
3114         return super.getStatsLogManager().withDefaultInstanceId(mAllAppsSessionLogId);
3115     }
3116 
3117     /**
3118      * Returns the current popup for testing, if any.
3119      */
3120     @VisibleForTesting
3121     @Nullable
getOptionsPopup()3122     public ArrowPopup<?> getOptionsPopup() {
3123         return findViewById(R.id.popup_container);
3124     }
3125 }
3126