1 /*
2  * Copyright (C) 2021 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.server.location.settings;
18 
19 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
20 
21 import android.content.Context;
22 import android.os.Environment;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.FgThread;
28 
29 import java.io.DataInput;
30 import java.io.DataOutput;
31 import java.io.File;
32 import java.io.IOException;
33 import java.util.concurrent.CopyOnWriteArrayList;
34 import java.util.function.Function;
35 
36 /**
37  * Accessor for location user settings. Ensure there is only ever one instance as multiple instances
38  * don't play nicely with each other.
39  */
40 public class LocationSettings {
41 
42     /** Listens for changes to location user settings. */
43     public interface LocationUserSettingsListener {
44         /** Invoked when location user settings have changed for the given user. */
onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings, LocationUserSettings newSettings)45         void onLocationUserSettingsChanged(int userId, LocationUserSettings oldSettings,
46                 LocationUserSettings newSettings);
47     }
48 
49     private static final String LOCATION_DIRNAME = "location";
50     private static final String LOCATION_SETTINGS_FILENAME = "settings";
51 
52     final Context mContext;
53 
54     @GuardedBy("mUserSettings")
55     private final SparseArray<LocationUserSettingsStore> mUserSettings;
56     private final CopyOnWriteArrayList<LocationUserSettingsListener> mUserSettingsListeners;
57 
LocationSettings(Context context)58     public LocationSettings(Context context) {
59         mContext = context;
60         mUserSettings = new SparseArray<>(1);
61         mUserSettingsListeners = new CopyOnWriteArrayList<>();
62     }
63 
64     /** Registers a listener for changes to location user settings. */
registerLocationUserSettingsListener(LocationUserSettingsListener listener)65     public final void registerLocationUserSettingsListener(LocationUserSettingsListener listener) {
66         mUserSettingsListeners.add(listener);
67     }
68 
69     /** Unregisters a listener for changes to location user settings. */
unregisterLocationUserSettingsListener( LocationUserSettingsListener listener)70     public final void unregisterLocationUserSettingsListener(
71             LocationUserSettingsListener listener) {
72         mUserSettingsListeners.remove(listener);
73     }
74 
getUserSettingsDir(int userId)75     protected File getUserSettingsDir(int userId) {
76         return Environment.getDataSystemDeDirectory(userId);
77     }
78 
createUserSettingsStore(int userId, File file)79     protected LocationUserSettingsStore createUserSettingsStore(int userId, File file) {
80         return new LocationUserSettingsStore(userId, file);
81     }
82 
getUserSettingsStore(int userId)83     private LocationUserSettingsStore getUserSettingsStore(int userId) {
84         synchronized (mUserSettings) {
85             LocationUserSettingsStore settingsStore = mUserSettings.get(userId);
86             if (settingsStore == null) {
87                 File file = new File(new File(getUserSettingsDir(userId), LOCATION_DIRNAME),
88                         LOCATION_SETTINGS_FILENAME);
89                 settingsStore = createUserSettingsStore(userId, file);
90                 mUserSettings.put(userId, settingsStore);
91             }
92             return settingsStore;
93         }
94     }
95 
96     /** Retrieves the current state of location user settings. */
getUserSettings(int userId)97     public final LocationUserSettings getUserSettings(int userId) {
98         return getUserSettingsStore(userId).get();
99     }
100 
101     /** Updates the current state of location user settings for the given user. */
updateUserSettings(int userId, Function<LocationUserSettings, LocationUserSettings> updater)102     public final void updateUserSettings(int userId,
103             Function<LocationUserSettings, LocationUserSettings> updater) {
104         getUserSettingsStore(userId).update(updater);
105     }
106 
107     @VisibleForTesting
flushFiles()108     final void flushFiles() throws InterruptedException {
109         synchronized (mUserSettings) {
110             int size = mUserSettings.size();
111             for (int i = 0; i < size; i++) {
112                 mUserSettings.valueAt(i).flushFile();
113             }
114         }
115     }
116 
117     @VisibleForTesting
deleteFiles()118     final void deleteFiles() throws InterruptedException {
119         synchronized (mUserSettings) {
120             int size = mUserSettings.size();
121             for (int i = 0; i < size; i++) {
122                 mUserSettings.valueAt(i).deleteFile();
123             }
124         }
125     }
126 
fireListeners(int userId, LocationUserSettings oldSettings, LocationUserSettings newSettings)127     protected final void fireListeners(int userId, LocationUserSettings oldSettings,
128             LocationUserSettings newSettings) {
129         for (LocationUserSettingsListener listener : mUserSettingsListeners) {
130             listener.onLocationUserSettingsChanged(userId, oldSettings, newSettings);
131         }
132     }
133 
134     class LocationUserSettingsStore extends SettingsStore<LocationUserSettings> {
135 
136         protected final int mUserId;
137 
LocationUserSettingsStore(int userId, File file)138         LocationUserSettingsStore(int userId, File file) {
139             super(file);
140             mUserId = userId;
141         }
142 
143         @Override
read(int version, DataInput in)144         protected LocationUserSettings read(int version, DataInput in) throws IOException {
145             return filterSettings(LocationUserSettings.read(mContext.getResources(), version, in));
146         }
147 
148         @Override
write(DataOutput out, LocationUserSettings settings)149         protected void write(DataOutput out, LocationUserSettings settings) throws IOException {
150             settings.write(out);
151         }
152 
153         @Override
update(Function<LocationUserSettings, LocationUserSettings> updater)154         public void update(Function<LocationUserSettings, LocationUserSettings> updater) {
155             super.update(settings -> filterSettings(updater.apply(settings)));
156         }
157 
158         @Override
onChange(LocationUserSettings oldSettings, LocationUserSettings newSettings)159         protected void onChange(LocationUserSettings oldSettings,
160                 LocationUserSettings newSettings) {
161             FgThread.getExecutor().execute(() -> fireListeners(mUserId, oldSettings, newSettings));
162         }
163 
filterSettings(LocationUserSettings settings)164         private LocationUserSettings filterSettings(LocationUserSettings settings) {
165             if (settings.isAdasGnssLocationEnabled()
166                     && !mContext.getPackageManager().hasSystemFeature(FEATURE_AUTOMOTIVE)) {
167                 // prevent non-automotive devices from ever enabling this
168                 settings = settings.withAdasGnssLocationEnabled(false);
169             }
170             return settings;
171         }
172     }
173 }
174