1 /* 2 * Copyright (C) 2023 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.model; 17 18 import static android.provider.BaseColumns._ID; 19 import static android.util.Base64.NO_PADDING; 20 import static android.util.Base64.NO_WRAP; 21 22 import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT; 23 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; 24 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; 25 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR; 26 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME; 27 import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb; 28 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY; 29 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL; 30 import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG; 31 import static com.android.launcher3.provider.LauncherDbUtils.tableExists; 32 33 import android.app.blob.BlobHandle; 34 import android.app.blob.BlobStoreManager; 35 import android.content.ContentResolver; 36 import android.content.ContentValues; 37 import android.content.Context; 38 import android.content.pm.PackageManager; 39 import android.content.pm.ProviderInfo; 40 import android.content.res.Resources; 41 import android.database.Cursor; 42 import android.database.SQLException; 43 import android.database.sqlite.SQLiteDatabase; 44 import android.net.Uri; 45 import android.os.Bundle; 46 import android.os.ParcelFileDescriptor; 47 import android.os.Process; 48 import android.os.UserHandle; 49 import android.os.UserManager; 50 import android.provider.Settings; 51 import android.text.TextUtils; 52 import android.util.Base64; 53 import android.util.Log; 54 import android.util.Xml; 55 56 import androidx.annotation.Nullable; 57 import androidx.annotation.WorkerThread; 58 59 import com.android.launcher3.AutoInstallsLayout; 60 import com.android.launcher3.AutoInstallsLayout.SourceResources; 61 import com.android.launcher3.ConstantItem; 62 import com.android.launcher3.DefaultLayoutParser; 63 import com.android.launcher3.EncryptionType; 64 import com.android.launcher3.InvariantDeviceProfile; 65 import com.android.launcher3.LauncherAppState; 66 import com.android.launcher3.LauncherFiles; 67 import com.android.launcher3.LauncherPrefs; 68 import com.android.launcher3.LauncherSettings; 69 import com.android.launcher3.LauncherSettings.Favorites; 70 import com.android.launcher3.Utilities; 71 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger; 72 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError; 73 import com.android.launcher3.logging.FileLog; 74 import com.android.launcher3.pm.UserCache; 75 import com.android.launcher3.provider.LauncherDbUtils; 76 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction; 77 import com.android.launcher3.provider.RestoreDbTask; 78 import com.android.launcher3.util.IOUtils; 79 import com.android.launcher3.util.IntArray; 80 import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext; 81 import com.android.launcher3.util.Partner; 82 import com.android.launcher3.widget.LauncherWidgetHolder; 83 84 import org.xmlpull.v1.XmlPullParser; 85 86 import java.io.InputStream; 87 import java.io.StringReader; 88 89 /** 90 * Utility class which maintains an instance of Launcher database and provides utility methods 91 * around it. 92 */ 93 public class ModelDbController { 94 private static final String TAG = "LauncherProvider"; 95 96 private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED"; 97 public static final String EXTRA_DB_NAME = "db_name"; 98 99 protected DatabaseHelper mOpenHelper; 100 101 private final Context mContext; 102 ModelDbController(Context context)103 public ModelDbController(Context context) { 104 mContext = context; 105 } 106 createDbIfNotExists()107 private synchronized void createDbIfNotExists() { 108 if (mOpenHelper == null) { 109 mOpenHelper = createDatabaseHelper(false /* forMigration */); 110 RestoreDbTask.restoreIfNeeded(mContext, this); 111 } 112 } 113 createDatabaseHelper(boolean forMigration)114 protected DatabaseHelper createDatabaseHelper(boolean forMigration) { 115 boolean isSandbox = mContext instanceof SandboxContext; 116 String dbName = isSandbox ? null : InvariantDeviceProfile.INSTANCE.get(mContext).dbFile; 117 118 // Set the flag for empty DB 119 Runnable onEmptyDbCreateCallback = forMigration ? () -> { } 120 : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true)); 121 122 DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName, 123 this::getSerialNumberForUser, onEmptyDbCreateCallback); 124 // Table creation sometimes fails silently, which leads to a crash loop. 125 // This way, we will try to create a table every time after crash, so the device 126 // would eventually be able to recover. 127 if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) { 128 Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate"); 129 // This operation is a no-op if the table already exists. 130 addTableToDb(databaseHelper.getWritableDatabase(), 131 getSerialNumberForUser(Process.myUserHandle()), 132 true /* optional */); 133 } 134 databaseHelper.mHotseatRestoreTableExists = tableExists( 135 databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE); 136 137 databaseHelper.initIds(); 138 return databaseHelper; 139 } 140 141 /** 142 * Refer {@link SQLiteDatabase#query} 143 */ 144 @WorkerThread query(String table, String[] projection, String selection, String[] selectionArgs, String sortOrder)145 public Cursor query(String table, String[] projection, String selection, 146 String[] selectionArgs, String sortOrder) { 147 createDbIfNotExists(); 148 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 149 Cursor result = db.query( 150 table, projection, selection, selectionArgs, null, null, sortOrder); 151 152 final Bundle extra = new Bundle(); 153 extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName()); 154 result.setExtras(extra); 155 return result; 156 } 157 158 /** 159 * Refer {@link SQLiteDatabase#insert(String, String, ContentValues)} 160 */ 161 @WorkerThread insert(String table, ContentValues initialValues)162 public int insert(String table, ContentValues initialValues) { 163 createDbIfNotExists(); 164 165 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 166 addModifiedTime(initialValues); 167 int rowId = mOpenHelper.dbInsertAndCheck(db, table, initialValues); 168 if (rowId >= 0) { 169 onAddOrDeleteOp(db); 170 } 171 return rowId; 172 } 173 174 /** 175 * Refer {@link SQLiteDatabase#delete(String, String, String[])} 176 */ 177 @WorkerThread delete(String table, String selection, String[] selectionArgs)178 public int delete(String table, String selection, String[] selectionArgs) { 179 createDbIfNotExists(); 180 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 181 182 int count = db.delete(table, selection, selectionArgs); 183 if (count > 0) { 184 onAddOrDeleteOp(db); 185 } 186 return count; 187 } 188 189 /** 190 * Refer {@link SQLiteDatabase#update(String, ContentValues, String, String[])} 191 */ 192 @WorkerThread update(String table, ContentValues values, String selection, String[] selectionArgs)193 public int update(String table, ContentValues values, 194 String selection, String[] selectionArgs) { 195 createDbIfNotExists(); 196 197 addModifiedTime(values); 198 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 199 int count = db.update(table, values, selection, selectionArgs); 200 return count; 201 } 202 203 /** 204 * Clears a previously set flag corresponding to empty db creation 205 */ 206 @WorkerThread clearEmptyDbFlag()207 public void clearEmptyDbFlag() { 208 createDbIfNotExists(); 209 clearFlagEmptyDbCreated(); 210 } 211 212 /** 213 * Generates an id to be used for new item in the favorites table 214 */ 215 @WorkerThread generateNewItemId()216 public int generateNewItemId() { 217 createDbIfNotExists(); 218 return mOpenHelper.generateNewItemId(); 219 } 220 221 /** 222 * Generates an id to be used for new workspace screen 223 */ 224 @WorkerThread getNewScreenId()225 public int getNewScreenId() { 226 createDbIfNotExists(); 227 return mOpenHelper.getNewScreenId(); 228 } 229 230 /** 231 * Creates an empty DB clearing all existing data 232 */ 233 @WorkerThread createEmptyDB()234 public void createEmptyDB() { 235 createDbIfNotExists(); 236 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 237 LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true)); 238 } 239 240 /** 241 * Removes any widget which are present in the framework, but not in out internal DB 242 */ 243 @WorkerThread removeGhostWidgets()244 public void removeGhostWidgets() { 245 createDbIfNotExists(); 246 mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase()); 247 } 248 249 /** 250 * Returns a new {@link SQLiteTransaction} 251 */ 252 @WorkerThread newTransaction()253 public SQLiteTransaction newTransaction() { 254 createDbIfNotExists(); 255 return new SQLiteTransaction(mOpenHelper.getWritableDatabase()); 256 } 257 258 /** 259 * Refreshes the internal state corresponding to presence of hotseat table 260 */ 261 @WorkerThread refreshHotseatRestoreTable()262 public void refreshHotseatRestoreTable() { 263 createDbIfNotExists(); 264 mOpenHelper.mHotseatRestoreTableExists = tableExists( 265 mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE); 266 } 267 268 269 /** 270 * Migrates the DB if needed. If the migration failed, it clears the DB. 271 */ tryMigrateDB(@ullable LauncherRestoreEventLogger restoreEventLogger)272 public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) { 273 274 if (!migrateGridIfNeeded()) { 275 if (restoreEventLogger != null) { 276 sendMetricsForFailedMigration(restoreEventLogger, getDb()); 277 } 278 FileLog.d(TAG, "Migration failed: resetting launcher database"); 279 createEmptyDB(); 280 LauncherPrefs.get(mContext).putSync( 281 getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true)); 282 283 // Write the grid state to avoid another migration 284 new DeviceGridState(LauncherAppState.getIDP(mContext)).writeToPrefs(mContext); 285 } 286 } 287 288 /** 289 * Migrates the DB if needed, and returns false if the migration failed 290 * and DB needs to be cleared. 291 * @return true if migration was success or ignored, false if migration failed 292 * and the DB should be reset. 293 */ migrateGridIfNeeded()294 private boolean migrateGridIfNeeded() { 295 createDbIfNotExists(); 296 if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) { 297 // If we have already create a new DB, ignore migration 298 Log.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration"); 299 return false; 300 } 301 InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); 302 if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) { 303 Log.d(TAG, "migrateGridIfNeeded: no grid migration needed"); 304 return true; 305 } 306 String targetDbName = new DeviceGridState(idp).getDbFile(); 307 if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) { 308 Log.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName); 309 return false; 310 } 311 DatabaseHelper oldHelper = mOpenHelper; 312 mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper 313 : createDatabaseHelper(true /* forMigration */); 314 try { 315 // This is the current grid we have, given by the mContext 316 DeviceGridState srcDeviceState = new DeviceGridState(mContext); 317 // This is the state we want to migrate to that is given by the idp 318 DeviceGridState destDeviceState = new DeviceGridState(idp); 319 return GridSizeMigrationUtil.migrateGridIfNeeded(mContext, srcDeviceState, 320 destDeviceState, mOpenHelper, oldHelper.getWritableDatabase()); 321 } catch (Exception e) { 322 FileLog.e(TAG, "Failed to migrate grid", e); 323 return false; 324 } finally { 325 if (mOpenHelper != oldHelper) { 326 oldHelper.close(); 327 } 328 } 329 } 330 331 /** 332 * In case of migration failure, report metrics for the count of each itemType in the DB. 333 * @param restoreEventLogger logger used to report Launcher restore metrics 334 */ sendMetricsForFailedMigration(LauncherRestoreEventLogger restoreEventLogger, SQLiteDatabase db)335 private void sendMetricsForFailedMigration(LauncherRestoreEventLogger restoreEventLogger, 336 SQLiteDatabase db) { 337 try (Cursor cursor = db.rawQuery( 338 "SELECT itemType, COUNT(*) AS count FROM favorites GROUP BY itemType", 339 null 340 )) { 341 if (cursor.moveToFirst()) { 342 do { 343 restoreEventLogger.logFavoritesItemsRestoreFailed( 344 cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)), 345 cursor.getInt(cursor.getColumnIndexOrThrow("count")), 346 RestoreError.GRID_MIGRATION_FAILURE 347 ); 348 } while (cursor.moveToNext()); 349 } 350 } catch (Exception e) { 351 FileLog.e(TAG, "sendMetricsForFailedDb: Error reading from database", e); 352 } 353 } 354 355 /** 356 * Returns the underlying model database 357 */ getDb()358 public SQLiteDatabase getDb() { 359 createDbIfNotExists(); 360 return mOpenHelper.getWritableDatabase(); 361 } 362 onAddOrDeleteOp(SQLiteDatabase db)363 private void onAddOrDeleteOp(SQLiteDatabase db) { 364 mOpenHelper.onAddOrDeleteOp(db); 365 } 366 367 /** 368 * Deletes any empty folder from the DB. 369 * @return Ids of deleted folders. 370 */ 371 @WorkerThread deleteEmptyFolders()372 public IntArray deleteEmptyFolders() { 373 createDbIfNotExists(); 374 375 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 376 try (SQLiteTransaction t = new SQLiteTransaction(db)) { 377 // Select folders whose id do not match any container value. 378 String selection = LauncherSettings.Favorites.ITEM_TYPE + " = " 379 + LauncherSettings.Favorites.ITEM_TYPE_FOLDER + " AND " 380 + LauncherSettings.Favorites._ID + " NOT IN (SELECT " 381 + LauncherSettings.Favorites.CONTAINER + " FROM " 382 + Favorites.TABLE_NAME + ")"; 383 384 IntArray folderIds = LauncherDbUtils.queryIntArray(false, db, Favorites.TABLE_NAME, 385 Favorites._ID, selection, null, null); 386 if (!folderIds.isEmpty()) { 387 db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery( 388 LauncherSettings.Favorites._ID, folderIds), null); 389 } 390 t.commit(); 391 return folderIds; 392 } catch (SQLException ex) { 393 Log.e(TAG, ex.getMessage(), ex); 394 return new IntArray(); 395 } 396 } 397 398 /** 399 * Deletes any app pair that doesn't contain 2 member apps from the DB. 400 * @return Ids of deleted app pairs. 401 */ 402 @WorkerThread deleteBadAppPairs()403 public IntArray deleteBadAppPairs() { 404 createDbIfNotExists(); 405 406 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 407 try (SQLiteTransaction t = new SQLiteTransaction(db)) { 408 // Select all entries with ITEM_TYPE = ITEM_TYPE_APP_PAIR whose id does not appear 409 // exactly twice in the CONTAINER column. 410 String selection = 411 ITEM_TYPE + " = " + ITEM_TYPE_APP_PAIR 412 + " AND " + _ID + " NOT IN" 413 + " (SELECT " + CONTAINER + " FROM " + TABLE_NAME 414 + " GROUP BY " + CONTAINER + " HAVING COUNT(*) = 2)"; 415 416 IntArray appPairIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME, 417 _ID, selection, null, null); 418 if (!appPairIds.isEmpty()) { 419 db.delete(TABLE_NAME, Utilities.createDbSelectionQuery( 420 _ID, appPairIds), null); 421 } 422 t.commit(); 423 return appPairIds; 424 } catch (SQLException ex) { 425 Log.e(TAG, ex.getMessage(), ex); 426 return new IntArray(); 427 } 428 } 429 430 /** 431 * Deletes any app with a container id that doesn't exist. 432 * @return Ids of deleted apps. 433 */ 434 @WorkerThread deleteUnparentedApps()435 public IntArray deleteUnparentedApps() { 436 createDbIfNotExists(); 437 438 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 439 try (SQLiteTransaction t = new SQLiteTransaction(db)) { 440 // Select all entries whose container id does not appear in the database. 441 String selection = 442 CONTAINER + " >= 0" 443 + " AND " + CONTAINER + " NOT IN" 444 + " (SELECT " + _ID + " FROM " + TABLE_NAME + ")"; 445 446 IntArray appIds = LauncherDbUtils.queryIntArray(false, db, TABLE_NAME, 447 _ID, selection, null, null); 448 if (!appIds.isEmpty()) { 449 db.delete(TABLE_NAME, Utilities.createDbSelectionQuery( 450 _ID, appIds), null); 451 } 452 t.commit(); 453 return appIds; 454 } catch (SQLException ex) { 455 Log.e(TAG, ex.getMessage(), ex); 456 return new IntArray(); 457 } 458 } 459 addModifiedTime(ContentValues values)460 private static void addModifiedTime(ContentValues values) { 461 values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis()); 462 } 463 clearFlagEmptyDbCreated()464 private void clearFlagEmptyDbCreated() { 465 LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey()); 466 } 467 468 /** 469 * Loads the default workspace based on the following priority scheme: 470 * 1) From the app restrictions 471 * 2) From a package provided by play store 472 * 3) From a partner configuration APK, already in the system image 473 * 4) The default configuration for the particular device 474 */ 475 @WorkerThread loadDefaultFavoritesIfNecessary()476 public synchronized void loadDefaultFavoritesIfNecessary() { 477 createDbIfNotExists(); 478 479 if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) { 480 Log.d(TAG, "loading default workspace"); 481 482 LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder(); 483 try { 484 AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHolder); 485 if (loader == null) { 486 loader = AutoInstallsLayout.get(mContext, widgetHolder, mOpenHelper); 487 } 488 if (loader == null) { 489 final Partner partner = Partner.get(mContext.getPackageManager()); 490 if (partner != null) { 491 int workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT); 492 if (workspaceResId != 0) { 493 loader = new DefaultLayoutParser(mContext, widgetHolder, 494 mOpenHelper, partner.getResources(), workspaceResId); 495 } 496 } 497 } 498 499 final boolean usingExternallyProvidedLayout = loader != null; 500 if (loader == null) { 501 loader = getDefaultLayoutParser(widgetHolder); 502 } 503 504 // There might be some partially restored DB items, due to buggy restore logic in 505 // previous versions of launcher. 506 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 507 // Populate favorites table with initial favorites 508 if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0) 509 && usingExternallyProvidedLayout) { 510 // Unable to load external layout. Cleanup and load the internal layout. 511 mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase()); 512 mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), 513 getDefaultLayoutParser(widgetHolder)); 514 } 515 clearFlagEmptyDbCreated(); 516 } finally { 517 widgetHolder.destroy(); 518 } 519 } 520 } 521 522 /** 523 * Creates workspace loader from an XML resource listed in the app restrictions. 524 * 525 * @return the loader if the restrictions are set and the resource exists; null otherwise. 526 */ createWorkspaceLoaderFromAppRestriction( LauncherWidgetHolder widgetHolder)527 private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction( 528 LauncherWidgetHolder widgetHolder) { 529 ContentResolver cr = mContext.getContentResolver(); 530 String blobHandlerDigest = Settings.Secure.getString(cr, LAYOUT_DIGEST_KEY); 531 if (!TextUtils.isEmpty(blobHandlerDigest)) { 532 BlobStoreManager blobManager = mContext.getSystemService(BlobStoreManager.class); 533 try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream( 534 blobManager.openBlob(BlobHandle.createWithSha256( 535 Base64.decode(blobHandlerDigest, NO_WRAP | NO_PADDING), 536 LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)))) { 537 return getAutoInstallsLayoutFromIS(in, widgetHolder, new SourceResources() { }); 538 } catch (Exception e) { 539 Log.e(TAG, "Error getting layout from blob handle" , e); 540 return null; 541 } 542 } 543 544 String authority = Settings.Secure.getString(cr, "launcher3.layout.provider"); 545 if (TextUtils.isEmpty(authority)) { 546 return null; 547 } 548 549 PackageManager pm = mContext.getPackageManager(); 550 ProviderInfo pi = pm.resolveContentProvider(authority, 0); 551 if (pi == null) { 552 Log.e(TAG, "No provider found for authority " + authority); 553 return null; 554 } 555 Uri uri = getLayoutUri(authority, mContext); 556 try (InputStream in = cr.openInputStream(uri)) { 557 Log.d(TAG, "Loading layout from " + authority); 558 559 Resources res = pm.getResourcesForApplication(pi.applicationInfo); 560 return getAutoInstallsLayoutFromIS(in, widgetHolder, SourceResources.wrap(res)); 561 } catch (Exception e) { 562 Log.e(TAG, "Error getting layout stream from: " + authority , e); 563 return null; 564 } 565 } 566 getAutoInstallsLayoutFromIS(InputStream in, LauncherWidgetHolder widgetHolder, SourceResources res)567 private AutoInstallsLayout getAutoInstallsLayoutFromIS(InputStream in, 568 LauncherWidgetHolder widgetHolder, SourceResources res) throws Exception { 569 // Read the full xml so that we fail early in case of any IO error. 570 String layout = new String(IOUtils.toByteArray(in)); 571 XmlPullParser parser = Xml.newPullParser(); 572 parser.setInput(new StringReader(layout)); 573 574 return new AutoInstallsLayout(mContext, widgetHolder, mOpenHelper, res, 575 () -> parser, AutoInstallsLayout.TAG_WORKSPACE); 576 } 577 getLayoutUri(String authority, Context ctx)578 public static Uri getLayoutUri(String authority, Context ctx) { 579 InvariantDeviceProfile grid = LauncherAppState.getIDP(ctx); 580 return new Uri.Builder().scheme("content").authority(authority).path("launcher_layout") 581 .appendQueryParameter("version", "1") 582 .appendQueryParameter("gridWidth", Integer.toString(grid.numColumns)) 583 .appendQueryParameter("gridHeight", Integer.toString(grid.numRows)) 584 .appendQueryParameter("hotseatSize", Integer.toString(grid.numDatabaseHotseatIcons)) 585 .build(); 586 } 587 getDefaultLayoutParser(LauncherWidgetHolder widgetHolder)588 private DefaultLayoutParser getDefaultLayoutParser(LauncherWidgetHolder widgetHolder) { 589 InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext); 590 int defaultLayout = idp.demoModeLayoutId != 0 591 && mContext.getSystemService(UserManager.class).isDemoUser() 592 ? idp.demoModeLayoutId : idp.defaultLayoutId; 593 594 return new DefaultLayoutParser(mContext, widgetHolder, 595 mOpenHelper, mContext.getResources(), defaultLayout); 596 } 597 getEmptyDbCreatedKey()598 private ConstantItem<Boolean> getEmptyDbCreatedKey() { 599 return getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()); 600 } 601 602 /** 603 * Re-composite given key in respect to database. If the current db is 604 * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to 605 * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning 606 * string will be "EMPTY_DATABASE_CREATED@minimal.db". 607 */ getEmptyDbCreatedKey(String dbName)608 private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) { 609 if (mContext instanceof SandboxContext) { 610 return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED, 611 false /* default value */, EncryptionType.ENCRYPTED); 612 } 613 String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB) 614 ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName; 615 return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED); 616 } 617 618 /** 619 * Returns the serial number for the provided user 620 */ getSerialNumberForUser(UserHandle user)621 public long getSerialNumberForUser(UserHandle user) { 622 return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user); 623 } 624 } 625