blob: edeb049c0802e2a1b0cceed3aa65f59b529b77cd [file] [log] [blame]
Chad Brubaker90f391f2018-10-19 10:26:19 -07001/*
2 * Copyright (C) 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
17package com.android.server;
18
19import static android.content.pm.PackageManager.PERMISSION_GRANTED;
20
Chad Brubaker90f391f2018-10-19 10:26:19 -070021import android.content.Context;
22import android.hardware.ISensorPrivacyListener;
23import android.hardware.ISensorPrivacyManager;
Chad Brubaker90f391f2018-10-19 10:26:19 -070024import android.os.Environment;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.Looper;
28import android.os.RemoteCallbackList;
29import android.os.RemoteException;
Chad Brubaker90f391f2018-10-19 10:26:19 -070030import android.util.ArrayMap;
31import android.util.AtomicFile;
32import android.util.Log;
33import android.util.Xml;
34
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.util.FastXmlSerializer;
37import com.android.internal.util.XmlUtils;
38import com.android.internal.util.function.pooled.PooledLambda;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.FileOutputStream;
47import java.io.IOException;
48import java.nio.charset.StandardCharsets;
49import java.util.NoSuchElementException;
50
51/** @hide */
52public final class SensorPrivacyService extends SystemService {
53
54 private static final String TAG = "SensorPrivacyService";
55
56 private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
57 private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy";
58 private static final String XML_ATTRIBUTE_ENABLED = "enabled";
59
60 private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
61
62 public SensorPrivacyService(Context context) {
63 super(context);
64 mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context);
65 }
66
67 @Override
68 public void onStart() {
69 publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl);
70 }
71
72 class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub {
73
74 private final SensorPrivacyHandler mHandler;
75 private final Context mContext;
76 private final Object mLock = new Object();
77 @GuardedBy("mLock")
78 private final AtomicFile mAtomicFile;
79 @GuardedBy("mLock")
80 private boolean mEnabled;
81
82 SensorPrivacyServiceImpl(Context context) {
83 mContext = context;
84 mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
85 File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
86 SENSOR_PRIVACY_XML_FILE);
87 mAtomicFile = new AtomicFile(sensorPrivacyFile);
88 synchronized (mLock) {
89 mEnabled = readPersistedSensorPrivacyEnabledLocked();
90 }
91 }
92
93 /**
94 * Sets the sensor privacy to the provided state and notifies all listeners of the new
95 * state.
96 */
97 @Override
98 public void setSensorPrivacy(boolean enable) {
99 enforceSensorPrivacyPermission();
100 synchronized (mLock) {
101 mEnabled = enable;
102 FileOutputStream outputStream = null;
103 try {
104 XmlSerializer serializer = new FastXmlSerializer();
105 outputStream = mAtomicFile.startWrite();
106 serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
107 serializer.startDocument(null, true);
108 serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
109 serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(enable));
110 serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
111 serializer.endDocument();
112 mAtomicFile.finishWrite(outputStream);
113 } catch (IOException e) {
114 Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
115 mAtomicFile.failWrite(outputStream);
116 }
117 }
118 mHandler.onSensorPrivacyChanged(enable);
119 }
120
121 /**
122 * Enforces the caller contains the necessary permission to change the state of sensor
123 * privacy.
124 */
125 private void enforceSensorPrivacyPermission() {
126 if (mContext.checkCallingOrSelfPermission(
127 android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) {
128 return;
129 }
130 throw new SecurityException(
131 "Changing sensor privacy requires the following permission: "
132 + android.Manifest.permission.MANAGE_SENSOR_PRIVACY);
133 }
134
135 /**
136 * Returns whether sensor privacy is enabled.
137 */
138 @Override
139 public boolean isSensorPrivacyEnabled() {
140 synchronized (mLock) {
141 return mEnabled;
142 }
143 }
144
145 /**
146 * Returns the state of sensor privacy from persistent storage.
147 */
148 private boolean readPersistedSensorPrivacyEnabledLocked() {
149 // if the file does not exist then sensor privacy has not yet been enabled on
150 // the device.
151 if (!mAtomicFile.exists()) {
152 return false;
153 }
154 boolean enabled;
155 try (FileInputStream inputStream = mAtomicFile.openRead()) {
156 XmlPullParser parser = Xml.newPullParser();
157 parser.setInput(inputStream, StandardCharsets.UTF_8.name());
158 XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
159 parser.next();
160 String tagName = parser.getName();
161 enabled = Boolean.valueOf(parser.getAttributeValue(null, XML_ATTRIBUTE_ENABLED));
162 } catch (IOException | XmlPullParserException e) {
163 Log.e(TAG, "Caught an exception reading the state from storage: ", e);
164 // Delete the file to prevent the same error on subsequent calls and assume sensor
165 // privacy is not enabled.
166 mAtomicFile.delete();
167 enabled = false;
168 }
169 return enabled;
170 }
171
172 /**
173 * Persists the state of sensor privacy.
174 */
175 private void persistSensorPrivacyState() {
176 synchronized (mLock) {
177 FileOutputStream outputStream = null;
178 try {
179 XmlSerializer serializer = new FastXmlSerializer();
180 outputStream = mAtomicFile.startWrite();
181 serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
182 serializer.startDocument(null, true);
183 serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
184 serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(mEnabled));
185 serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
186 serializer.endDocument();
187 mAtomicFile.finishWrite(outputStream);
188 } catch (IOException e) {
189 Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e);
190 mAtomicFile.failWrite(outputStream);
191 }
192 }
193 }
194
195 /**
196 * Registers a listener to be notified when the sensor privacy state changes.
197 */
198 @Override
199 public void addSensorPrivacyListener(ISensorPrivacyListener listener) {
200 if (listener == null) {
201 throw new NullPointerException("listener cannot be null");
202 }
203 mHandler.addListener(listener);
204 }
205
206 /**
207 * Unregisters a listener from sensor privacy state change notifications.
208 */
209 @Override
210 public void removeSensorPrivacyListener(ISensorPrivacyListener listener) {
211 if (listener == null) {
212 throw new NullPointerException("listener cannot be null");
213 }
214 mHandler.removeListener(listener);
215 }
216 }
217
218 /**
219 * Handles sensor privacy state changes and notifying listeners of the change.
220 */
221 private final class SensorPrivacyHandler extends Handler {
222 private static final int MESSAGE_SENSOR_PRIVACY_CHANGED = 1;
223
224 private final Object mListenerLock = new Object();
225
226 @GuardedBy("mListenerLock")
227 private final RemoteCallbackList<ISensorPrivacyListener> mListeners =
228 new RemoteCallbackList<>();
229 private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients;
230 private final Context mContext;
231
232 SensorPrivacyHandler(Looper looper, Context context) {
233 super(looper);
234 mDeathRecipients = new ArrayMap<>();
235 mContext = context;
236 }
237
238 public void onSensorPrivacyChanged(boolean enabled) {
239 sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
240 this, enabled));
241 sendMessage(
242 PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState,
243 mSensorPrivacyServiceImpl));
244 }
245
246 public void addListener(ISensorPrivacyListener listener) {
247 synchronized (mListenerLock) {
248 DeathRecipient deathRecipient = new DeathRecipient(listener);
249 mDeathRecipients.put(listener, deathRecipient);
250 mListeners.register(listener);
251 }
252 }
253
254 public void removeListener(ISensorPrivacyListener listener) {
255 synchronized (mListenerLock) {
256 DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
257 if (deathRecipient != null) {
258 deathRecipient.destroy();
259 }
260 mListeners.unregister(listener);
261 }
262 }
263
264 public void handleSensorPrivacyChanged(boolean enabled) {
265 final int count = mListeners.beginBroadcast();
266 for (int i = 0; i < count; i++) {
267 ISensorPrivacyListener listener = mListeners.getBroadcastItem(i);
268 try {
269 listener.onSensorPrivacyChanged(enabled);
270 } catch (RemoteException e) {
271 Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
272 }
273 }
274 mListeners.finishBroadcast();
Chad Brubaker90f391f2018-10-19 10:26:19 -0700275 }
276 }
277
278 private final class DeathRecipient implements IBinder.DeathRecipient {
279
280 private ISensorPrivacyListener mListener;
281
282 DeathRecipient(ISensorPrivacyListener listener) {
283 mListener = listener;
284 try {
285 mListener.asBinder().linkToDeath(this, 0);
286 } catch (RemoteException e) {
287 }
288 }
289
290 @Override
291 public void binderDied() {
292 mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener);
293 }
294
295 public void destroy() {
296 try {
297 mListener.asBinder().unlinkToDeath(this, 0);
298 } catch (NoSuchElementException e) {
299 }
300 }
301 }
Chad Brubaker90f391f2018-10-19 10:26:19 -0700302}