| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.controls.controller |
| |
| import android.app.job.JobInfo |
| import android.app.job.JobParameters |
| import android.app.job.JobService |
| import android.content.ComponentName |
| import android.content.Context |
| import android.os.PersistableBundle |
| import com.android.internal.annotations.VisibleForTesting |
| import com.android.systemui.backup.BackupHelper |
| import com.android.systemui.settings.UserFileManagerImpl |
| import java.io.File |
| import java.util.concurrent.Executor |
| import java.util.concurrent.TimeUnit |
| |
| /** |
| * Class to track the auxiliary persistence of controls. |
| * |
| * This file is a copy of the `controls_favorites.xml` file restored from a back up. It is used to |
| * keep track of controls that were restored but its corresponding app has not been installed yet. |
| */ |
| class AuxiliaryPersistenceWrapper |
| @VisibleForTesting |
| internal constructor(wrapper: ControlsFavoritePersistenceWrapper) { |
| |
| constructor( |
| file: File, |
| executor: Executor |
| ) : this(ControlsFavoritePersistenceWrapper(file, executor)) |
| |
| companion object { |
| const val AUXILIARY_FILE_NAME = "aux_controls_favorites.xml" |
| } |
| |
| private var persistenceWrapper: ControlsFavoritePersistenceWrapper = wrapper |
| |
| /** Access the current list of favorites as tracked by the auxiliary file */ |
| var favorites: List<StructureInfo> = emptyList() |
| private set |
| |
| init { |
| initialize() |
| } |
| |
| /** |
| * Change the file that this class is tracking. |
| * |
| * This will reset [favorites]. |
| */ |
| fun changeFile(file: File) { |
| persistenceWrapper.changeFileAndBackupManager(file, null) |
| initialize() |
| } |
| |
| /** |
| * Initialize the list of favorites to the content of the auxiliary file. If the file does not |
| * exist, it will be initialized to an empty list. |
| */ |
| fun initialize() { |
| favorites = |
| if (persistenceWrapper.fileExists) { |
| persistenceWrapper.readFavorites() |
| } else { |
| emptyList() |
| } |
| } |
| |
| /** |
| * Gets the list of favorite controls as persisted in the auxiliary file for a given component. |
| * |
| * When the favorites for that application are returned, they will be removed from the auxiliary |
| * file immediately, so they won't be retrieved again. |
| * |
| * @param componentName the name of the service that provided the controls |
| * @return a list of structures with favorites |
| */ |
| fun getCachedFavoritesAndRemoveFor(componentName: ComponentName): List<StructureInfo> { |
| if (!persistenceWrapper.fileExists) { |
| return emptyList() |
| } |
| val (comp, noComp) = favorites.partition { it.componentName == componentName } |
| return comp.also { |
| favorites = noComp |
| if (favorites.isNotEmpty()) { |
| persistenceWrapper.storeFavorites(noComp) |
| } else { |
| persistenceWrapper.deleteFile() |
| } |
| } |
| } |
| |
| /** [JobService] to delete the auxiliary file after a week. */ |
| class DeletionJobService : JobService() { |
| companion object { |
| @VisibleForTesting internal val DELETE_FILE_JOB_ID = 1000 |
| @VisibleForTesting internal val USER = "USER" |
| private val WEEK_IN_MILLIS = TimeUnit.DAYS.toMillis(7) |
| fun getJobForContext(context: Context, targetUserId: Int): JobInfo { |
| val jobId = DELETE_FILE_JOB_ID + context.userId |
| val componentName = ComponentName(context, DeletionJobService::class.java) |
| val bundle = PersistableBundle().also { it.putInt(USER, targetUserId) } |
| return JobInfo.Builder(jobId, componentName) |
| .setMinimumLatency(WEEK_IN_MILLIS) |
| .setPersisted(true) |
| .setExtras(bundle) |
| .build() |
| } |
| } |
| |
| @VisibleForTesting |
| fun attachContext(context: Context) { |
| attachBaseContext(context) |
| } |
| |
| override fun onStartJob(params: JobParameters): Boolean { |
| val userId = params.getExtras()?.getInt(USER, 0) ?: 0 |
| synchronized(BackupHelper.controlsDataLock) { |
| val file = |
| UserFileManagerImpl.createFile( |
| userId = userId, |
| fileName = AUXILIARY_FILE_NAME, |
| ) |
| baseContext.deleteFile(file.getPath()) |
| } |
| return false |
| } |
| |
| override fun onStopJob(params: JobParameters?): Boolean { |
| return true // reschedule and try again if the job was stopped without completing |
| } |
| } |
| } |