/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.content.Context; import android.hardware.ISensorPrivacyListener; import android.hardware.ISensorPrivacyManager; import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Log; import android.util.Xml; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.NoSuchElementException; /** @hide */ public final class SensorPrivacyService extends SystemService { private static final String TAG = "SensorPrivacyService"; private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml"; private static final String XML_TAG_SENSOR_PRIVACY = "sensor-privacy"; private static final String XML_ATTRIBUTE_ENABLED = "enabled"; private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl; public SensorPrivacyService(Context context) { super(context); mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl(context); } @Override public void onStart() { publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl); } class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub { private final SensorPrivacyHandler mHandler; private final Context mContext; private final Object mLock = new Object(); @GuardedBy("mLock") private final AtomicFile mAtomicFile; @GuardedBy("mLock") private boolean mEnabled; SensorPrivacyServiceImpl(Context context) { mContext = context; mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext); File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(), SENSOR_PRIVACY_XML_FILE); mAtomicFile = new AtomicFile(sensorPrivacyFile); synchronized (mLock) { mEnabled = readPersistedSensorPrivacyEnabledLocked(); } } /** * Sets the sensor privacy to the provided state and notifies all listeners of the new * state. */ @Override public void setSensorPrivacy(boolean enable) { enforceSensorPrivacyPermission(); synchronized (mLock) { mEnabled = enable; FileOutputStream outputStream = null; try { XmlSerializer serializer = new FastXmlSerializer(); outputStream = mAtomicFile.startWrite(); serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(enable)); serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); serializer.endDocument(); mAtomicFile.finishWrite(outputStream); } catch (IOException e) { Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e); mAtomicFile.failWrite(outputStream); } } mHandler.onSensorPrivacyChanged(enable); } /** * Enforces the caller contains the necessary permission to change the state of sensor * privacy. */ private void enforceSensorPrivacyPermission() { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.MANAGE_SENSOR_PRIVACY) == PERMISSION_GRANTED) { return; } throw new SecurityException( "Changing sensor privacy requires the following permission: " + android.Manifest.permission.MANAGE_SENSOR_PRIVACY); } /** * Returns whether sensor privacy is enabled. */ @Override public boolean isSensorPrivacyEnabled() { synchronized (mLock) { return mEnabled; } } /** * Returns the state of sensor privacy from persistent storage. */ private boolean readPersistedSensorPrivacyEnabledLocked() { // if the file does not exist then sensor privacy has not yet been enabled on // the device. if (!mAtomicFile.exists()) { return false; } boolean enabled; try (FileInputStream inputStream = mAtomicFile.openRead()) { XmlPullParser parser = Xml.newPullParser(); parser.setInput(inputStream, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY); parser.next(); String tagName = parser.getName(); enabled = Boolean.valueOf(parser.getAttributeValue(null, XML_ATTRIBUTE_ENABLED)); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Caught an exception reading the state from storage: ", e); // Delete the file to prevent the same error on subsequent calls and assume sensor // privacy is not enabled. mAtomicFile.delete(); enabled = false; } return enabled; } /** * Persists the state of sensor privacy. */ private void persistSensorPrivacyState() { synchronized (mLock) { FileOutputStream outputStream = null; try { XmlSerializer serializer = new FastXmlSerializer(); outputStream = mAtomicFile.startWrite(); serializer.setOutput(outputStream, StandardCharsets.UTF_8.name()); serializer.startDocument(null, true); serializer.startTag(null, XML_TAG_SENSOR_PRIVACY); serializer.attribute(null, XML_ATTRIBUTE_ENABLED, String.valueOf(mEnabled)); serializer.endTag(null, XML_TAG_SENSOR_PRIVACY); serializer.endDocument(); mAtomicFile.finishWrite(outputStream); } catch (IOException e) { Log.e(TAG, "Caught an exception persisting the sensor privacy state: ", e); mAtomicFile.failWrite(outputStream); } } } /** * Registers a listener to be notified when the sensor privacy state changes. */ @Override public void addSensorPrivacyListener(ISensorPrivacyListener listener) { if (listener == null) { throw new NullPointerException("listener cannot be null"); } mHandler.addListener(listener); } /** * Unregisters a listener from sensor privacy state change notifications. */ @Override public void removeSensorPrivacyListener(ISensorPrivacyListener listener) { if (listener == null) { throw new NullPointerException("listener cannot be null"); } mHandler.removeListener(listener); } } /** * Handles sensor privacy state changes and notifying listeners of the change. */ private final class SensorPrivacyHandler extends Handler { private static final int MESSAGE_SENSOR_PRIVACY_CHANGED = 1; private final Object mListenerLock = new Object(); @GuardedBy("mListenerLock") private final RemoteCallbackList<ISensorPrivacyListener> mListeners = new RemoteCallbackList<>(); private final ArrayMap<ISensorPrivacyListener, DeathRecipient> mDeathRecipients; private final Context mContext; SensorPrivacyHandler(Looper looper, Context context) { super(looper); mDeathRecipients = new ArrayMap<>(); mContext = context; } public void onSensorPrivacyChanged(boolean enabled) { sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged, this, enabled)); sendMessage( PooledLambda.obtainMessage(SensorPrivacyServiceImpl::persistSensorPrivacyState, mSensorPrivacyServiceImpl)); } public void addListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = new DeathRecipient(listener); mDeathRecipients.put(listener, deathRecipient); mListeners.register(listener); } } public void removeListener(ISensorPrivacyListener listener) { synchronized (mListenerLock) { DeathRecipient deathRecipient = mDeathRecipients.remove(listener); if (deathRecipient != null) { deathRecipient.destroy(); } mListeners.unregister(listener); } } public void handleSensorPrivacyChanged(boolean enabled) { final int count = mListeners.beginBroadcast(); for (int i = 0; i < count; i++) { ISensorPrivacyListener listener = mListeners.getBroadcastItem(i); try { listener.onSensorPrivacyChanged(enabled); } catch (RemoteException e) { Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e); } } mListeners.finishBroadcast(); } } private final class DeathRecipient implements IBinder.DeathRecipient { private ISensorPrivacyListener mListener; DeathRecipient(ISensorPrivacyListener listener) { mListener = listener; try { mListener.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { } } @Override public void binderDied() { mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener); } public void destroy() { try { mListener.asBinder().unlinkToDeath(this, 0); } catch (NoSuchElementException e) { } } } }