1 /*
2  * Copyright 2017 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 androidx.work.impl.utils
17 
18 import android.content.Context
19 import android.content.SharedPreferences
20 import androidx.sqlite.db.SupportSQLiteDatabase
21 import androidx.work.impl.WorkDatabase
22 import androidx.work.impl.model.Preference
23 import androidx.work.impl.utils.PreferenceUtils.INSERT_PREFERENCE
24 import java.util.concurrent.Callable
25 
26 /**
27  * Generates unique IDs that are persisted in [SharedPreferences].
28  *
29  * @param workDatabase The [WorkDatabase] where metadata is persisted.
30  */
31 class IdGenerator(private val workDatabase: WorkDatabase) {
32     /** Generates IDs for [android.app.job.JobInfo] jobs given a reserved range. */
nextJobSchedulerIdWithRangenull33     fun nextJobSchedulerIdWithRange(minInclusive: Int, maxInclusive: Int): Int {
34         return workDatabase.runInTransaction(
35             Callable {
36                 var id = workDatabase.nextId(NEXT_JOB_SCHEDULER_ID_KEY)
37                 if (id !in minInclusive..maxInclusive) {
38                     // outside the range, re-start at minInclusive.
39                     id = minInclusive
40                     workDatabase.updatePreference(NEXT_JOB_SCHEDULER_ID_KEY, id + 1)
41                 }
42                 id
43             }
44         )
45     }
46 
47     /** Generates IDs for [android.app.AlarmManager] work. */
nextAlarmManagerIdnull48     fun nextAlarmManagerId(): Int {
49         return workDatabase.runInTransaction(
50             Callable { workDatabase.nextId(NEXT_ALARM_MANAGER_ID_KEY) }
51         )
52     }
53 }
54 
nextIdnull55 private fun WorkDatabase.nextId(key: String): Int {
56     val value = preferenceDao().getLongValue(key)
57     val id = value?.toInt() ?: INITIAL_ID
58     val nextId = if (id == Int.MAX_VALUE) INITIAL_ID else id + 1
59     updatePreference(key, nextId)
60     return id
61 }
62 
WorkDatabasenull63 private fun WorkDatabase.updatePreference(key: String, value: Int) =
64     this.preferenceDao().insertPreference(Preference(key, value.toLong()))
65 
66 /** The initial id used for JobInfos and Alarms. */
67 const val INITIAL_ID = 0
68 const val NEXT_JOB_SCHEDULER_ID_KEY = "next_job_scheduler_id"
69 const val NEXT_ALARM_MANAGER_ID_KEY = "next_alarm_manager_id"
70 const val PREFERENCE_FILE_KEY = "androidx.work.util.id"
71 
72 /**
73  * Migrates [IdGenerator] from [android.content.SharedPreferences] to the [WorkDatabase].
74  *
75  * @param context The application [Context]
76  */
77 internal fun migrateLegacyIdGenerator(context: Context, sqLiteDatabase: SupportSQLiteDatabase) {
78     val sharedPreferences = context.getSharedPreferences(PREFERENCE_FILE_KEY, Context.MODE_PRIVATE)
79 
80     // Check to see if we have not migrated already.
81     if (
82         sharedPreferences.contains(NEXT_JOB_SCHEDULER_ID_KEY) ||
83             sharedPreferences.contains(NEXT_JOB_SCHEDULER_ID_KEY)
84     ) {
85         val nextJobId = sharedPreferences.getInt(NEXT_JOB_SCHEDULER_ID_KEY, INITIAL_ID)
86         val nextAlarmId = sharedPreferences.getInt(NEXT_ALARM_MANAGER_ID_KEY, INITIAL_ID)
87         sqLiteDatabase.beginTransaction()
88         try {
89             sqLiteDatabase.execSQL(INSERT_PREFERENCE, arrayOf(NEXT_JOB_SCHEDULER_ID_KEY, nextJobId))
90             sqLiteDatabase.execSQL(
91                 INSERT_PREFERENCE,
92                 arrayOf(NEXT_ALARM_MANAGER_ID_KEY, nextAlarmId)
93             )
94             // Cleanup
95             sharedPreferences.edit().clear().apply()
96             sqLiteDatabase.setTransactionSuccessful()
97         } finally {
98             sqLiteDatabase.endTransaction()
99         }
100     }
101 }
102