Home | History | Annotate | Download | only in server
      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