1 /* 2 * Copyright 2018 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 androidx.work.impl.utils; 18 19 import static android.content.Context.MODE_PRIVATE; 20 21 import android.content.Context; 22 import android.content.SharedPreferences; 23 24 import androidx.annotation.RestrictTo; 25 import androidx.lifecycle.LiveData; 26 import androidx.lifecycle.Transformations; 27 import androidx.sqlite.db.SupportSQLiteDatabase; 28 import androidx.work.impl.WorkDatabase; 29 import androidx.work.impl.model.Preference; 30 31 import org.jspecify.annotations.NonNull; 32 33 /** 34 * Preference Utils for WorkManager. 35 * 36 */ 37 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 38 public class PreferenceUtils { 39 public static final String INSERT_PREFERENCE = 40 "INSERT OR REPLACE INTO `Preference`" 41 + " (`key`, `long_value`) VALUES" 42 + " (@key, @long_value)"; 43 44 public static final String CREATE_PREFERENCE = 45 "CREATE TABLE IF NOT EXISTS `Preference` (`key` TEXT NOT NULL, `long_value` INTEGER, " 46 + "PRIMARY KEY(`key`))"; 47 48 // For migration 49 public static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences"; 50 public static final String KEY_LAST_CANCEL_ALL_TIME_MS = "last_cancel_all_time_ms"; 51 public static final String KEY_RESCHEDULE_NEEDED = "reschedule_needed"; 52 private static final String KEY_LAST_FORCE_STOP_MS = "last_force_stop_ms"; 53 54 private final WorkDatabase mWorkDatabase; 55 PreferenceUtils(@onNull WorkDatabase workDatabase)56 public PreferenceUtils(@NonNull WorkDatabase workDatabase) { 57 mWorkDatabase = workDatabase; 58 } 59 60 /** 61 * @return The last time (in milliseconds) a {@code cancelAll} method was called 62 */ getLastCancelAllTimeMillis()63 public long getLastCancelAllTimeMillis() { 64 Long value = 65 mWorkDatabase.preferenceDao().getLongValue(KEY_LAST_CANCEL_ALL_TIME_MS); 66 67 return value != null ? value : 0L; 68 } 69 70 /** 71 * @return A {@link LiveData} of the last time (in milliseconds) a {@code cancelAll} method was 72 * called 73 */ getLastCancelAllTimeMillisLiveData()74 public @NonNull LiveData<Long> getLastCancelAllTimeMillisLiveData() { 75 LiveData<Long> observableValue = 76 mWorkDatabase.preferenceDao().getObservableLongValue(KEY_LAST_CANCEL_ALL_TIME_MS); 77 return Transformations.map(observableValue, (Long value) -> value != null ? value : 0L); 78 } 79 80 /** 81 * Sets the last time a {@code cancelAll} method was called 82 * 83 * @param timeMillis The time a {@code cancelAll} method was called (in milliseconds) 84 */ setLastCancelAllTimeMillis(final long timeMillis)85 public void setLastCancelAllTimeMillis(final long timeMillis) { 86 Preference preference = new Preference(KEY_LAST_CANCEL_ALL_TIME_MS, timeMillis); 87 mWorkDatabase.preferenceDao().insertPreference(preference); 88 } 89 90 /** 91 * @return {@code true} When we should reschedule workers. 92 */ getNeedsReschedule()93 public boolean getNeedsReschedule() { 94 // This preference is being set by a Room Migration. 95 Long value = mWorkDatabase.preferenceDao().getLongValue(KEY_RESCHEDULE_NEEDED); 96 return value != null && value == 1L; 97 } 98 99 /** 100 * Updates the key which indicates that we have rescheduled jobs. 101 */ setNeedsReschedule(boolean needsReschedule)102 public void setNeedsReschedule(boolean needsReschedule) { 103 Preference preference = new Preference(KEY_RESCHEDULE_NEEDED, needsReschedule); 104 mWorkDatabase.preferenceDao().insertPreference(preference); 105 } 106 107 /** 108 * Updates the key which indicates the last force-stop timestamp handled by 109 * {@link androidx.work.WorkManager}. 110 */ setLastForceStopEventMillis(long lastForceStopTimeMillis)111 public void setLastForceStopEventMillis(long lastForceStopTimeMillis) { 112 Preference preference = new Preference(KEY_LAST_FORCE_STOP_MS, lastForceStopTimeMillis); 113 mWorkDatabase.preferenceDao().insertPreference(preference); 114 } 115 116 /** 117 * Gets the timestamp for the last known force-stop event that 118 * {@link androidx.work.WorkManager} is aware of. 119 */ getLastForceStopEventMillis()120 public long getLastForceStopEventMillis() { 121 Long timestamp = mWorkDatabase.preferenceDao().getLongValue(KEY_LAST_FORCE_STOP_MS); 122 if (timestamp != null) { 123 return timestamp; 124 } else { 125 return 0; 126 } 127 } 128 129 /** 130 * Migrates preferences from {@link android.content.SharedPreferences} to the 131 * {@link WorkDatabase}. 132 * 133 * @param context The application {@link Context} 134 */ migrateLegacyPreferences( @onNull Context context, @NonNull SupportSQLiteDatabase sqLiteDatabase)135 public static void migrateLegacyPreferences( 136 @NonNull Context context, 137 @NonNull SupportSQLiteDatabase sqLiteDatabase) { 138 139 SharedPreferences sharedPreferences = 140 context.getSharedPreferences(PREFERENCES_FILE_NAME, MODE_PRIVATE); 141 142 // Check to see if we have not migrated already. 143 if (sharedPreferences.contains(KEY_RESCHEDULE_NEEDED) 144 || sharedPreferences.contains(KEY_LAST_CANCEL_ALL_TIME_MS)) { 145 146 long lastCancelTimeMillis = sharedPreferences.getLong(KEY_LAST_CANCEL_ALL_TIME_MS, 0L); 147 boolean needsReschedule = sharedPreferences.getBoolean(KEY_RESCHEDULE_NEEDED, false); 148 long reschedule = needsReschedule ? 1L : 0L; 149 sqLiteDatabase.beginTransaction(); 150 try { 151 152 sqLiteDatabase.execSQL(INSERT_PREFERENCE, 153 new Object[] {KEY_LAST_CANCEL_ALL_TIME_MS, lastCancelTimeMillis}); 154 155 sqLiteDatabase.execSQL(INSERT_PREFERENCE, 156 new Object[] {KEY_RESCHEDULE_NEEDED, reschedule}); 157 158 // Delete 159 sharedPreferences.edit() 160 .clear() 161 .apply(); 162 163 sqLiteDatabase.setTransactionSuccessful(); 164 } finally { 165 sqLiteDatabase.endTransaction(); 166 } 167 } 168 } 169 } 170