1 /* 2 * Copyright (C) 2020 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.systemui.controls.controller 18 19 import android.app.job.JobInfo 20 import android.app.job.JobParameters 21 import android.app.job.JobService 22 import android.content.ComponentName 23 import android.content.Context 24 import android.os.PersistableBundle 25 import com.android.internal.annotations.VisibleForTesting 26 import com.android.systemui.backup.BackupHelper 27 import com.android.systemui.settings.UserFileManagerImpl 28 import java.io.File 29 import java.util.concurrent.Executor 30 import java.util.concurrent.TimeUnit 31 32 /** 33 * Class to track the auxiliary persistence of controls. 34 * 35 * This file is a copy of the `controls_favorites.xml` file restored from a back up. It is used to 36 * keep track of controls that were restored but its corresponding app has not been installed yet. 37 */ 38 class AuxiliaryPersistenceWrapper 39 @VisibleForTesting 40 internal constructor(wrapper: ControlsFavoritePersistenceWrapper) { 41 42 constructor( 43 file: File, 44 executor: Executor 45 ) : this(ControlsFavoritePersistenceWrapper(file, executor)) 46 47 companion object { 48 const val AUXILIARY_FILE_NAME = "aux_controls_favorites.xml" 49 } 50 51 private var persistenceWrapper: ControlsFavoritePersistenceWrapper = wrapper 52 53 /** Access the current list of favorites as tracked by the auxiliary file */ 54 var favorites: List<StructureInfo> = emptyList() 55 private set 56 57 init { 58 initialize() 59 } 60 61 /** 62 * Change the file that this class is tracking. 63 * 64 * This will reset [favorites]. 65 */ changeFilenull66 fun changeFile(file: File) { 67 persistenceWrapper.changeFileAndBackupManager(file, null) 68 initialize() 69 } 70 71 /** 72 * Initialize the list of favorites to the content of the auxiliary file. If the file does not 73 * exist, it will be initialized to an empty list. 74 */ initializenull75 fun initialize() { 76 favorites = 77 if (persistenceWrapper.fileExists) { 78 persistenceWrapper.readFavorites() 79 } else { 80 emptyList() 81 } 82 } 83 84 /** 85 * Gets the list of favorite controls as persisted in the auxiliary file for a given component. 86 * 87 * When the favorites for that application are returned, they will be removed from the auxiliary 88 * file immediately, so they won't be retrieved again. 89 * 90 * @param componentName the name of the service that provided the controls 91 * @return a list of structures with favorites 92 */ getCachedFavoritesAndRemoveFornull93 fun getCachedFavoritesAndRemoveFor(componentName: ComponentName): List<StructureInfo> { 94 if (!persistenceWrapper.fileExists) { 95 return emptyList() 96 } 97 val (comp, noComp) = favorites.partition { it.componentName == componentName } 98 return comp.also { 99 favorites = noComp 100 if (favorites.isNotEmpty()) { 101 persistenceWrapper.storeFavorites(noComp) 102 } else { 103 persistenceWrapper.deleteFile() 104 } 105 } 106 } 107 108 /** [JobService] to delete the auxiliary file after a week. */ 109 class DeletionJobService : JobService() { 110 companion object { 111 @VisibleForTesting internal val DELETE_FILE_JOB_ID = 1000 112 @VisibleForTesting internal val USER = "USER" 113 private val WEEK_IN_MILLIS = TimeUnit.DAYS.toMillis(7) getJobForContextnull114 fun getJobForContext(context: Context, targetUserId: Int): JobInfo { 115 val jobId = DELETE_FILE_JOB_ID + context.userId 116 val componentName = ComponentName(context, DeletionJobService::class.java) 117 val bundle = PersistableBundle().also { it.putInt(USER, targetUserId) } 118 return JobInfo.Builder(jobId, componentName) 119 .setMinimumLatency(WEEK_IN_MILLIS) 120 .setPersisted(true) 121 .setExtras(bundle) 122 .build() 123 } 124 } 125 126 @VisibleForTesting attachContextnull127 fun attachContext(context: Context) { 128 attachBaseContext(context) 129 } 130 onStartJobnull131 override fun onStartJob(params: JobParameters): Boolean { 132 val userId = params.getExtras()?.getInt(USER, 0) ?: 0 133 synchronized(BackupHelper.controlsDataLock) { 134 val file = 135 UserFileManagerImpl.createFile( 136 userId = userId, 137 fileName = AUXILIARY_FILE_NAME, 138 ) 139 baseContext.deleteFile(file.getPath()) 140 } 141 return false 142 } 143 onStopJobnull144 override fun onStopJob(params: JobParameters?): Boolean { 145 return true // reschedule and try again if the job was stopped without completing 146 } 147 } 148 } 149