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