1 /* <lambda>null2 * 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 package com.android.launcher3 17 18 import android.content.Context 19 import android.content.Intent 20 import android.content.pm.ShortcutInfo 21 import android.os.UserHandle 22 import android.text.TextUtils 23 import android.util.Pair 24 import androidx.annotation.WorkerThread 25 import com.android.launcher3.celllayout.CellPosMapper 26 import com.android.launcher3.dagger.ApplicationContext 27 import com.android.launcher3.dagger.LauncherAppSingleton 28 import com.android.launcher3.icons.IconCache 29 import com.android.launcher3.model.AddWorkspaceItemsTask 30 import com.android.launcher3.model.AllAppsList 31 import com.android.launcher3.model.BaseLauncherBinder.BaseLauncherBinderFactory 32 import com.android.launcher3.model.BgDataModel 33 import com.android.launcher3.model.CacheDataUpdatedTask 34 import com.android.launcher3.model.ItemInstallQueue 35 import com.android.launcher3.model.LoaderTask 36 import com.android.launcher3.model.LoaderTask.LoaderTaskFactory 37 import com.android.launcher3.model.ModelDbController 38 import com.android.launcher3.model.ModelDelegate 39 import com.android.launcher3.model.ModelInitializer 40 import com.android.launcher3.model.ModelLauncherCallbacks 41 import com.android.launcher3.model.ModelTaskController 42 import com.android.launcher3.model.ModelWriter 43 import com.android.launcher3.model.PackageUpdatedTask 44 import com.android.launcher3.model.ReloadStringCacheTask 45 import com.android.launcher3.model.ShortcutsChangedTask 46 import com.android.launcher3.model.UserLockStateChangedTask 47 import com.android.launcher3.model.UserManagerState 48 import com.android.launcher3.model.WorkspaceItemSpaceFinder 49 import com.android.launcher3.model.data.ItemInfo 50 import com.android.launcher3.model.data.WorkspaceItemInfo 51 import com.android.launcher3.pm.UserCache 52 import com.android.launcher3.shortcuts.ShortcutRequest 53 import com.android.launcher3.util.DaggerSingletonTracker 54 import com.android.launcher3.util.Executors.MAIN_EXECUTOR 55 import com.android.launcher3.util.Executors.MODEL_EXECUTOR 56 import com.android.launcher3.util.PackageUserKey 57 import com.android.launcher3.util.Preconditions 58 import java.io.FileDescriptor 59 import java.io.PrintWriter 60 import java.util.concurrent.CancellationException 61 import java.util.function.Consumer 62 import javax.inject.Inject 63 import javax.inject.Named 64 import javax.inject.Provider 65 66 /** 67 * Maintains in-memory state of the Launcher. It is expected that there should be only one 68 * LauncherModel object held in a static. Also provide APIs for updating the database state for the 69 * Launcher. 70 */ 71 @LauncherAppSingleton 72 class LauncherModel 73 @Inject 74 constructor( 75 @ApplicationContext private val context: Context, 76 private val taskControllerProvider: Provider<ModelTaskController>, 77 private val iconCache: IconCache, 78 private val prefs: LauncherPrefs, 79 private val installQueue: ItemInstallQueue, 80 @Named("ICONS_DB") dbFileName: String?, 81 initializer: ModelInitializer, 82 lifecycle: DaggerSingletonTracker, 83 val modelDelegate: ModelDelegate, 84 private val mBgAllAppsList: AllAppsList, 85 private val mBgDataModel: BgDataModel, 86 private val loaderFactory: LoaderTaskFactory, 87 private val binderFactory: BaseLauncherBinderFactory, 88 private val spaceFinderFactory: Provider<WorkspaceItemSpaceFinder>, 89 val modelDbController: ModelDbController, 90 ) { 91 92 private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1) 93 94 private val mLock = Any() 95 96 private var mLoaderTask: LoaderTask? = null 97 private var mIsLoaderTaskRunning = false 98 99 // only allow this once per reboot to reload work apps 100 private var mShouldReloadWorkProfile = true 101 102 // Indicates whether the current model data is valid or not. 103 // We start off with everything not loaded. After that, we assume that 104 // our monitoring of the package manager provides all updates and we never 105 // need to do a requery. This is only ever touched from the loader thread. 106 private var mModelLoaded = false 107 private var mModelDestroyed = false 108 109 fun isModelLoaded() = 110 synchronized(mLock) { mModelLoaded && mLoaderTask == null && !mModelDestroyed } 111 112 /** 113 * Returns the ID for the last model load. If the load ID doesn't match for a transaction, the 114 * transaction should be ignored. 115 */ 116 var lastLoadId: Int = -1 117 private set 118 119 // Runnable to check if the shortcuts permission has changed. 120 private val mDataValidationCheck = Runnable { 121 if (mModelLoaded) { 122 modelDelegate.validateData() 123 } 124 } 125 126 init { 127 if (!dbFileName.isNullOrEmpty()) { 128 initializer.initialize(this) 129 } 130 lifecycle.addCloseable { destroy() } 131 modelDelegate.init(this, mBgAllAppsList, mBgDataModel) 132 } 133 134 fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask) 135 136 /** Adds the provided items to the workspace. */ 137 fun addAndBindAddedWorkspaceItems(itemList: List<Pair<ItemInfo?, Any?>?>) { 138 callbacks.forEach { it.preAddApps() } 139 enqueueModelUpdateTask(AddWorkspaceItemsTask(itemList, spaceFinderFactory.get())) 140 } 141 142 fun getWriter( 143 verifyChanges: Boolean, 144 cellPosMapper: CellPosMapper?, 145 owner: BgDataModel.Callbacks?, 146 ) = ModelWriter(context, this, mBgDataModel, verifyChanges, cellPosMapper, owner) 147 148 /** Called when the icon for an app changes, outside of package event */ 149 @WorkerThread 150 fun onAppIconChanged(packageName: String, user: UserHandle) { 151 // Update the icon for the calendar package 152 enqueueModelUpdateTask(PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageName)) 153 ShortcutRequest(context, user).forPackage(packageName).query(ShortcutRequest.PINNED).let { 154 if (it.isNotEmpty()) { 155 enqueueModelUpdateTask(ShortcutsChangedTask(packageName, it, user, false)) 156 } 157 } 158 } 159 160 /** Called when the workspace items have drastically changed */ 161 fun onWorkspaceUiChanged() { 162 MODEL_EXECUTOR.execute(modelDelegate::workspaceLoadComplete) 163 } 164 165 /** Called when the model is destroyed */ 166 fun destroy() { 167 mModelDestroyed = true 168 MODEL_EXECUTOR.execute { modelDelegate.destroy() } 169 } 170 171 fun reloadStringCache() { 172 enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate)) 173 } 174 175 /** 176 * Called then there use a user event 177 * 178 * @see UserCache.addUserEventListener 179 */ 180 fun onUserEvent(user: UserHandle, action: String) { 181 when (action) { 182 Intent.ACTION_MANAGED_PROFILE_AVAILABLE -> { 183 if (mShouldReloadWorkProfile) { 184 forceReload() 185 } else { 186 enqueueModelUpdateTask( 187 PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user) 188 ) 189 } 190 mShouldReloadWorkProfile = false 191 } 192 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE -> { 193 mShouldReloadWorkProfile = false 194 enqueueModelUpdateTask( 195 PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user) 196 ) 197 } 198 UserCache.ACTION_PROFILE_LOCKED -> 199 enqueueModelUpdateTask(UserLockStateChangedTask(user, false)) 200 UserCache.ACTION_PROFILE_UNLOCKED -> 201 enqueueModelUpdateTask(UserLockStateChangedTask(user, true)) 202 Intent.ACTION_MANAGED_PROFILE_REMOVED -> { 203 prefs.put(LauncherPrefs.WORK_EDU_STEP, 0) 204 forceReload() 205 } 206 UserCache.ACTION_PROFILE_ADDED, 207 UserCache.ACTION_PROFILE_REMOVED -> forceReload() 208 UserCache.ACTION_PROFILE_AVAILABLE, 209 UserCache.ACTION_PROFILE_UNAVAILABLE -> { 210 // This broadcast is only available when android.os.Flags.allowPrivateProfile() is 211 // set. For Work-profile this broadcast will be sent in addition to 212 // ACTION_MANAGED_PROFILE_AVAILABLE/UNAVAILABLE. So effectively, this if block only 213 // handles the non-work profile case. 214 enqueueModelUpdateTask( 215 PackageUpdatedTask(PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user) 216 ) 217 } 218 } 219 } 220 221 /** 222 * Reloads the workspace items from the DB and re-binds the workspace. This should generally not 223 * be called as DB updates are automatically followed by UI update 224 */ 225 fun forceReload() { 226 synchronized(mLock) { 227 // Stop any existing loaders first, so they don't set mModelLoaded to true later 228 stopLoader() 229 mModelLoaded = false 230 } 231 rebindCallbacks() 232 } 233 234 /** Reloads the model if it is already in use */ 235 fun reloadIfActive() { 236 val wasActive: Boolean 237 synchronized(mLock) { wasActive = mModelLoaded || stopLoader() } 238 if (wasActive) forceReload() 239 } 240 241 /** Rebinds all existing callbacks with already loaded model */ 242 fun rebindCallbacks() { 243 if (hasCallbacks()) { 244 startLoader() 245 } 246 } 247 248 /** Removes an existing callback */ 249 fun removeCallbacks(callbacks: BgDataModel.Callbacks) { 250 synchronized(mCallbacksList) { 251 Preconditions.assertUIThread() 252 if (mCallbacksList.remove(callbacks)) { 253 if (stopLoader()) { 254 // Rebind existing callbacks 255 startLoader() 256 } 257 } 258 } 259 } 260 261 /** 262 * Adds a callbacks to receive model updates 263 * 264 * @return true if workspace load was performed synchronously 265 */ 266 fun addCallbacksAndLoad(callbacks: BgDataModel.Callbacks): Boolean { 267 synchronized(mLock) { 268 addCallbacks(callbacks) 269 return startLoader(arrayOf(callbacks)) 270 } 271 } 272 273 /** Adds a callbacks to receive model updates */ 274 fun addCallbacks(callbacks: BgDataModel.Callbacks) { 275 Preconditions.assertUIThread() 276 synchronized(mCallbacksList) { mCallbacksList.add(callbacks) } 277 } 278 279 /** 280 * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible. 281 * 282 * @return true if the page could be bound synchronously. 283 */ 284 fun startLoader() = startLoader(arrayOf()) 285 286 private fun startLoader(newCallbacks: Array<BgDataModel.Callbacks>): Boolean { 287 // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems 288 installQueue.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING) 289 synchronized(mLock) { 290 // If there is already one running, tell it to stop. 291 val wasRunning = stopLoader() 292 val bindDirectly = mModelLoaded && !mIsLoaderTaskRunning 293 val bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.isEmpty() 294 val callbacksList = if (bindAllCallbacks) callbacks else newCallbacks 295 if (callbacksList.isNotEmpty()) { 296 // Clear any pending bind-runnables from the synchronized load process. 297 callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) } 298 299 val launcherBinder = binderFactory.createBinder(callbacksList) 300 if (bindDirectly) { 301 // Divide the set of loaded items into those that we are binding synchronously, 302 // and everything else that is to be bound normally (asynchronously). 303 launcherBinder.bindWorkspace(bindAllCallbacks, /* isBindSync= */ true) 304 // For now, continue posting the binding of AllApps as there are other 305 // issues that arise from that. 306 launcherBinder.bindAllApps() 307 launcherBinder.bindDeepShortcuts() 308 launcherBinder.bindWidgets() 309 return true 310 } else { 311 val task = loaderFactory.newLoaderTask(launcherBinder, UserManagerState()) 312 mLoaderTask = task 313 314 // Always post the loader task, instead of running directly 315 // (even on same thread) so that we exit any nested synchronized blocks 316 MODEL_EXECUTOR.post(task) 317 } 318 } 319 } 320 return false 321 } 322 323 /** 324 * If there is already a loader task running, tell it to stop. 325 * 326 * @return true if an existing loader was stopped. 327 */ 328 private fun stopLoader(): Boolean { 329 synchronized(mLock) { 330 val oldTask: LoaderTask? = mLoaderTask 331 mLoaderTask = null 332 if (oldTask != null) { 333 oldTask.stopLocked() 334 return true 335 } 336 return false 337 } 338 } 339 340 /** 341 * Loads the model if not loaded 342 * 343 * @param callback called with the data model upon successful load or null on model thread. 344 */ 345 fun loadAsync(callback: Consumer<BgDataModel?>) { 346 synchronized(mLock) { 347 if (!mModelLoaded && !mIsLoaderTaskRunning) { 348 startLoader() 349 } 350 } 351 MODEL_EXECUTOR.post { callback.accept(if (isModelLoaded()) mBgDataModel else null) } 352 } 353 354 inner class LoaderTransaction(task: LoaderTask) : AutoCloseable { 355 private var mTask: LoaderTask? = null 356 357 init { 358 synchronized(mLock) { 359 if (mLoaderTask !== task) { 360 throw CancellationException("Loader already stopped") 361 } 362 this@LauncherModel.lastLoadId++ 363 mTask = task 364 mIsLoaderTaskRunning = true 365 mModelLoaded = false 366 } 367 } 368 369 fun commit() { 370 synchronized(mLock) { 371 // Everything loaded bind the data. 372 mModelLoaded = true 373 } 374 } 375 376 override fun close() { 377 synchronized(mLock) { 378 // If we are still the last one to be scheduled, remove ourselves. 379 if (mLoaderTask === mTask) { 380 mLoaderTask = null 381 } 382 mIsLoaderTaskRunning = false 383 } 384 } 385 } 386 387 @Throws(CancellationException::class) 388 fun beginLoader(task: LoaderTask) = LoaderTransaction(task) 389 390 /** 391 * Refreshes the cached shortcuts if the shortcut permission has changed. Current implementation 392 * simply reloads the workspace, but it can be optimized to use partial updates similar to 393 * [UserCache] 394 */ 395 fun validateModelDataOnResume() { 396 MODEL_EXECUTOR.handler.removeCallbacks(mDataValidationCheck) 397 MODEL_EXECUTOR.post(mDataValidationCheck) 398 } 399 400 /** Called when the icons for packages have been updated in the icon cache. */ 401 fun onPackageIconsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) { 402 // If any package icon has changed (app was updated while launcher was dead), 403 // update the corresponding shortcuts. 404 enqueueModelUpdateTask( 405 CacheDataUpdatedTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages) 406 ) 407 } 408 409 /** Called when the labels for the widgets has updated in the icon cache. */ 410 fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) { 411 enqueueModelUpdateTask { taskController, dataModel, _ -> 412 dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user) 413 taskController.bindUpdatedWidgets(dataModel) 414 } 415 } 416 417 fun enqueueModelUpdateTask(task: ModelUpdateTask) { 418 if (mModelDestroyed) { 419 return 420 } 421 MODEL_EXECUTOR.execute { 422 if (!isModelLoaded()) { 423 // Loader has not yet run. 424 return@execute 425 } 426 task.execute(taskControllerProvider.get(), mBgDataModel, mBgAllAppsList) 427 } 428 } 429 430 /** 431 * A task to be executed on the current callbacks on the UI thread. If there is no current 432 * callbacks, the task is ignored. 433 */ 434 fun interface CallbackTask { 435 fun execute(callbacks: BgDataModel.Callbacks) 436 } 437 438 fun interface ModelUpdateTask { 439 fun execute(taskController: ModelTaskController, dataModel: BgDataModel, apps: AllAppsList) 440 } 441 442 fun updateAndBindWorkspaceItem(si: WorkspaceItemInfo, info: ShortcutInfo) { 443 enqueueModelUpdateTask { taskController, _, _ -> 444 si.updateFromDeepShortcutInfo(info, context) 445 iconCache.getShortcutIcon(si, info) 446 taskController.getModelWriter().updateItemInDatabase(si) 447 taskController.bindUpdatedWorkspaceItems(listOf(si)) 448 } 449 } 450 451 fun refreshAndBindWidgetsAndShortcuts(packageUser: PackageUserKey?) { 452 enqueueModelUpdateTask { taskController, dataModel, _ -> 453 dataModel.widgetsModel.update(packageUser) 454 taskController.bindUpdatedWidgets(dataModel) 455 } 456 } 457 458 fun dumpState(prefix: String?, fd: FileDescriptor?, writer: PrintWriter, args: Array<String?>) { 459 if (args.isNotEmpty() && TextUtils.equals(args[0], "--all")) { 460 writer.println(prefix + "All apps list: size=" + mBgAllAppsList.data.size) 461 for (info in mBgAllAppsList.data) { 462 writer.println( 463 "$prefix title=\"${info.title}\" bitmapIcon=${info.bitmap.icon} componentName=${info.targetPackage}" 464 ) 465 } 466 writer.println() 467 } 468 modelDelegate.dump(prefix, fd, writer, args) 469 mBgDataModel.dump(prefix, fd, writer, args) 470 } 471 472 /** Returns true if there are any callbacks attached to the model */ 473 fun hasCallbacks() = synchronized(mCallbacksList) { mCallbacksList.isNotEmpty() } 474 475 /** Returns an array of currently attached callbacks */ 476 val callbacks: Array<BgDataModel.Callbacks> 477 get() { 478 synchronized(mCallbacksList) { 479 return mCallbacksList.toTypedArray<BgDataModel.Callbacks>() 480 } 481 } 482 483 companion object { 484 const val TAG = "Launcher.Model" 485 } 486 } 487