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