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