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