1 /* 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 17 package com.android.server; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import android.content.Context; 22 import android.hardware.ISensorPrivacyListener; 23 import android.hardware.ISensorPrivacyManager; 24 import android.os.Environment; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.Looper; 28 import android.os.RemoteCallbackList; 29 import android.os.RemoteException; 30 import android.util.ArrayMap; 31 import android.util.AtomicFile; 32 import android.util.Log; 33 import android.util.Xml; 34 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.internal.util.FastXmlSerializer; 37 import com.android.internal.util.XmlUtils; 38 import com.android.internal.util.function.pooled.PooledLambda; 39 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 import org.xmlpull.v1.XmlSerializer; 43 44 import java.io.File; 45 import java.io.FileInputStream; 46 import java.io.FileOutputStream; 47 import java.io.IOException; 48 import java.nio.charset.StandardCharsets; 49 import java.util.NoSuchElementException; 50 51 /** @hide */ 52 public 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(); 275 } 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 } 302 } 303