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.sensorprivacy;
18 
19 import android.annotation.NonNull;
20 import android.os.Environment;
21 import android.os.Handler;
22 import android.util.AtomicFile;
23 import android.util.Log;
24 import android.util.Xml;
25 
26 import com.android.internal.util.XmlUtils;
27 import com.android.internal.util.dump.DualDumpOutputStream;
28 import com.android.internal.util.function.pooled.PooledLambda;
29 import com.android.modules.utils.TypedXmlPullParser;
30 import com.android.modules.utils.TypedXmlSerializer;
31 import com.android.server.IoThread;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileOutputStream;
39 import java.io.IOException;
40 import java.util.Objects;
41 
42 class AllSensorStateController {
43 
44     private static final String LOG_TAG = AllSensorStateController.class.getSimpleName();
45 
46     private static final String SENSOR_PRIVACY_XML_FILE = "sensor_privacy.xml";
47     private static final String XML_TAG_SENSOR_PRIVACY = "all-sensor-privacy";
48     private static final String XML_TAG_SENSOR_PRIVACY_LEGACY = "sensor-privacy";
49     private static final String XML_ATTRIBUTE_ENABLED = "enabled";
50 
51     private static AllSensorStateController sInstance;
52 
53     private final AtomicFile mAtomicFile =
54             new AtomicFile(new File(Environment.getDataSystemDirectory(), SENSOR_PRIVACY_XML_FILE));
55 
56     private boolean mEnabled;
57     private SensorPrivacyStateController.AllSensorPrivacyListener mListener;
58     private Handler mListenerHandler;
59 
getInstance()60     static AllSensorStateController getInstance() {
61         if (sInstance == null) {
62             sInstance = new AllSensorStateController();
63         }
64         return sInstance;
65     }
66 
AllSensorStateController()67     private AllSensorStateController() {
68         if (!mAtomicFile.exists()) {
69             return;
70         }
71         try (FileInputStream inputStream = mAtomicFile.openRead()) {
72             TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
73 
74             while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
75                 String tagName = parser.getName();
76                 if (XML_TAG_SENSOR_PRIVACY.equals(tagName)) {
77                     mEnabled |= XmlUtils
78                             .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
79                     break;
80                 }
81                 if (XML_TAG_SENSOR_PRIVACY_LEGACY.equals(tagName)) {
82                     mEnabled |= XmlUtils
83                             .readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED, false);
84                 }
85                 if ("user".equals(tagName)) { // Migrate from mic/cam toggles format
86                     int user = XmlUtils.readIntAttribute(parser, "id", -1);
87                     if (user == 0) {
88                         mEnabled |=
89                                 XmlUtils.readBooleanAttribute(parser, XML_ATTRIBUTE_ENABLED);
90                     }
91                 }
92                 XmlUtils.nextElement(parser);
93             }
94         } catch (IOException | XmlPullParserException e) {
95             Log.e(LOG_TAG, "Caught an exception reading the state from storage: ", e);
96             mEnabled = false;
97         }
98     }
99 
getAllSensorStateLocked()100     public boolean getAllSensorStateLocked() {
101         return mEnabled;
102     }
103 
setAllSensorStateLocked(boolean enabled)104     public void setAllSensorStateLocked(boolean enabled) {
105         if (mEnabled != enabled) {
106             mEnabled = enabled;
107             if (mListener != null && mListenerHandler != null) {
108                 mListenerHandler.sendMessage(
109                         PooledLambda.obtainMessage(mListener::onAllSensorPrivacyChanged, enabled));
110             }
111         }
112     }
113 
setAllSensorPrivacyListenerLocked(Handler handler, SensorPrivacyStateController.AllSensorPrivacyListener listener)114     void setAllSensorPrivacyListenerLocked(Handler handler,
115             SensorPrivacyStateController.AllSensorPrivacyListener listener) {
116         Objects.requireNonNull(handler);
117         Objects.requireNonNull(listener);
118         if (mListener != null) {
119             throw new IllegalStateException("Listener is already set");
120         }
121         mListener = listener;
122         mListenerHandler = handler;
123     }
124 
schedulePersistLocked()125     public void schedulePersistLocked() {
126         IoThread.getHandler().sendMessage(PooledLambda.obtainMessage(this::persist, mEnabled));
127     }
128 
persist(boolean enabled)129     private void persist(boolean enabled) {
130         FileOutputStream outputStream = null;
131         try {
132             outputStream = mAtomicFile.startWrite();
133             TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
134             serializer.startDocument(null, true);
135             serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
136             serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
137             serializer.endTag(null, XML_TAG_SENSOR_PRIVACY);
138             serializer.endDocument();
139             mAtomicFile.finishWrite(outputStream);
140         } catch (IOException e) {
141             Log.e(LOG_TAG, "Caught an exception persisting the sensor privacy state: ", e);
142             mAtomicFile.failWrite(outputStream);
143         }
144     }
145 
resetForTesting()146     void resetForTesting() {
147         mListener = null;
148         mListenerHandler = null;
149         mEnabled = false;
150     }
151 
dumpLocked(@onNull DualDumpOutputStream dumpStream)152     void dumpLocked(@NonNull DualDumpOutputStream dumpStream) {
153         // TODO stub
154     }
155 }
156