1 /*
2  * Copyright (C) 2019 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.keyguard.clock;
17 
18 import android.annotation.Nullable;
19 import android.content.ContentResolver;
20 import android.provider.Settings;
21 import android.util.Log;
22 
23 import com.android.internal.annotations.VisibleForTesting;
24 
25 import org.json.JSONException;
26 import org.json.JSONObject;
27 
28 /**
29  * Wrapper around Settings used for testing.
30  */
31 public class SettingsWrapper {
32 
33     private static final String TAG = "ClockFaceSettings";
34     private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE;
35     private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE;
36     private static final String CLOCK_FIELD = "clock";
37 
38     private final ContentResolver mContentResolver;
39     private final Migration mMigration;
40 
SettingsWrapper(ContentResolver contentResolver)41     SettingsWrapper(ContentResolver contentResolver) {
42         this(contentResolver, new Migrator(contentResolver));
43     }
44 
45     @VisibleForTesting
SettingsWrapper(ContentResolver contentResolver, Migration migration)46     SettingsWrapper(ContentResolver contentResolver, Migration migration) {
47         mContentResolver = contentResolver;
48         mMigration = migration;
49     }
50 
51     /**
52      * Gets the value stored in settings for the custom clock face.
53      *
54      * @param userId ID of the user.
55      */
getLockScreenCustomClockFace(int userId)56     String getLockScreenCustomClockFace(int userId) {
57         return decode(
58                 Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId),
59                 userId);
60     }
61 
62     /**
63      * Gets the value stored in settings for the clock face to use when docked.
64      *
65      * @param userId ID of the user.
66      */
getDockedClockFace(int userId)67     String getDockedClockFace(int userId) {
68         return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId);
69     }
70 
71     /**
72      * Decodes the string stored in settings, which should be formatted as JSON.
73      * @param value String stored in settings. If value is not JSON, then the settings is
74      *              overwritten with JSON containing the prior value.
75      * @return ID of the clock face to show on AOD and lock screen. If value is not JSON, the value
76      *         is returned.
77      */
78     @VisibleForTesting
decode(@ullable String value, int userId)79     String decode(@Nullable String value, int userId) {
80         if (value == null) {
81             return value;
82         }
83         JSONObject json;
84         try {
85             json = new JSONObject(value);
86         } catch (JSONException ex) {
87             Log.e(TAG, "Settings value is not valid JSON", ex);
88             // The settings value isn't JSON since it didn't parse so migrate the value to JSON.
89             // TODO(b/135674383): Remove this migration path in the following release.
90             mMigration.migrate(value, userId);
91             return value;
92         }
93         try {
94             return json.getString(CLOCK_FIELD);
95         } catch (JSONException ex) {
96             Log.e(TAG, "JSON object does not contain clock field.", ex);
97             return null;
98         }
99     }
100 
101     interface Migration {
migrate(String value, int userId)102         void migrate(String value, int userId);
103     }
104 
105     /**
106      * Implementation of {@link Migration} that writes valid JSON back to Settings.
107      */
108     private static final class Migrator implements Migration {
109 
110         private final ContentResolver mContentResolver;
111 
Migrator(ContentResolver contentResolver)112         Migrator(ContentResolver contentResolver) {
113             mContentResolver = contentResolver;
114         }
115 
116         /**
117          * Migrate settings values that don't parse by converting to JSON format.
118          *
119          * Values in settings must be JSON to be backed up and restored. To help users maintain
120          * their current settings, convert existing values into the JSON format.
121          *
122          * TODO(b/135674383): Remove this migration code in the following release.
123          */
124         @Override
migrate(String value, int userId)125         public void migrate(String value, int userId) {
126             try {
127                 JSONObject json = new JSONObject();
128                 json.put(CLOCK_FIELD, value);
129                 Settings.Secure.putStringForUser(mContentResolver, CUSTOM_CLOCK_FACE,
130                         json.toString(),
131                         userId);
132             } catch (JSONException ex) {
133                 Log.e(TAG, "Failed migrating settings value to JSON format", ex);
134             }
135         }
136     }
137 }
138