blob: b0d6d65fdd4c39ec6771389f544ffc0ec979aef9 [file] [log] [blame]
/*
* 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.Manifest.permission.MANAGE_SENSOR_PRIVACY;
import static android.app.ActivityManager.RunningServiceInfo;
import static android.app.ActivityManager.RunningTaskInfo;
import static android.app.ActivityManager.getCurrentUser;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.drawable.Icon;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManagerInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.SensorPrivacyIndividualEnabledSensorProto;
import android.service.SensorPrivacyServiceDumpProto;
import android.service.SensorPrivacyUserProto;
import android.service.voice.VoiceInteractionManagerInternal;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.telephony.emergency.EmergencyNumber;
import android.text.Html;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.util.proto.ProtoOutputStream;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.pm.UserManagerInternal;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
/** @hide */
public final class SensorPrivacyService extends SystemService {
private static final String TAG = SensorPrivacyService.class.getSimpleName();
/** Version number indicating compatibility parsing the persisted file */
private static final int CURRENT_PERSISTENCE_VERSION = 1;
/** Version number indicating the persisted data needs upgraded to match new internal data
* structures and features */
private static final int CURRENT_VERSION = 1;
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_TAG_USER = "user";
private static final String XML_TAG_INDIVIDUAL_SENSOR_PRIVACY = "individual-sensor-privacy";
private static final String XML_ATTRIBUTE_ID = "id";
private static final String XML_ATTRIBUTE_PERSISTENCE_VERSION = "persistence-version";
private static final String XML_ATTRIBUTE_VERSION = "version";
private static final String XML_ATTRIBUTE_ENABLED = "enabled";
private static final String XML_ATTRIBUTE_SENSOR = "sensor";
private static final String SENSOR_PRIVACY_CHANNEL_ID = Context.SENSOR_PRIVACY_SERVICE;
private static final String ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY =
SensorPrivacyService.class.getName() + ".action.disable_sensor_privacy";
// These are associated with fields that existed for older persisted versions of files
private static final int VER0_ENABLED = 0;
private static final int VER0_INDIVIDUAL_ENABLED = 1;
private static final int VER1_ENABLED = 0;
private static final int VER1_INDIVIDUAL_ENABLED = 1;
public static final int REMINDER_DIALOG_DELAY_MILLIS = 500;
private final Context mContext;
private final SensorPrivacyServiceImpl mSensorPrivacyServiceImpl;
private final UserManagerInternal mUserManagerInternal;
private final ActivityManager mActivityManager;
private final ActivityTaskManager mActivityTaskManager;
private final AppOpsManager mAppOpsManager;
private final TelephonyManager mTelephonyManager;
private final IBinder mAppOpsRestrictionToken = new Binder();
private SensorPrivacyManagerInternalImpl mSensorPrivacyManagerInternal;
private EmergencyCallHelper mEmergencyCallHelper;
private KeyguardManager mKeyguardManager;
public SensorPrivacyService(Context context) {
super(context);
mContext = context;
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUserManagerInternal = getLocalService(UserManagerInternal.class);
mActivityManager = context.getSystemService(ActivityManager.class);
mActivityTaskManager = context.getSystemService(ActivityTaskManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mSensorPrivacyServiceImpl = new SensorPrivacyServiceImpl();
}
@Override
public void onStart() {
publishBinderService(Context.SENSOR_PRIVACY_SERVICE, mSensorPrivacyServiceImpl);
mSensorPrivacyManagerInternal = new SensorPrivacyManagerInternalImpl();
publishLocalService(SensorPrivacyManagerInternal.class,
mSensorPrivacyManagerInternal);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
mEmergencyCallHelper = new EmergencyCallHelper();
}
}
class SensorPrivacyServiceImpl extends ISensorPrivacyManager.Stub implements
AppOpsManager.OnOpNotedListener, AppOpsManager.OnOpStartedListener,
IBinder.DeathRecipient, UserManagerInternal.UserRestrictionsListener {
private final SensorPrivacyHandler mHandler;
private final Object mLock = new Object();
@GuardedBy("mLock")
private final AtomicFile mAtomicFile;
@GuardedBy("mLock")
private SparseBooleanArray mEnabled = new SparseBooleanArray();
@GuardedBy("mLock")
private SparseArray<SparseBooleanArray> mIndividualEnabled = new SparseArray<>();
/**
* Packages for which not to show sensor use reminders.
*
* <Package, User> -> list of suppressor tokens
*/
@GuardedBy("mLock")
private ArrayMap<Pair<String, UserHandle>, ArrayList<IBinder>> mSuppressReminders =
new ArrayMap<>();
private final ArrayMap<SensorUseReminderDialogInfo, ArraySet<Integer>>
mQueuedSensorUseReminderDialogs = new ArrayMap<>();
private class SensorUseReminderDialogInfo {
private int mTaskId;
private UserHandle mUser;
private String mPackageName;
SensorUseReminderDialogInfo(int taskId, UserHandle user, String packageName) {
mTaskId = taskId;
mUser = user;
mPackageName = packageName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof SensorUseReminderDialogInfo)) return false;
SensorUseReminderDialogInfo that = (SensorUseReminderDialogInfo) o;
return mTaskId == that.mTaskId
&& Objects.equals(mUser, that.mUser)
&& Objects.equals(mPackageName, that.mPackageName);
}
@Override
public int hashCode() {
return Objects.hash(mTaskId, mUser, mPackageName);
}
}
SensorPrivacyServiceImpl() {
mHandler = new SensorPrivacyHandler(FgThread.get().getLooper(), mContext);
File sensorPrivacyFile = new File(Environment.getDataSystemDirectory(),
SENSOR_PRIVACY_XML_FILE);
mAtomicFile = new AtomicFile(sensorPrivacyFile);
synchronized (mLock) {
if (readPersistedSensorPrivacyStateLocked()) {
persistSensorPrivacyStateLocked();
}
for (int i = 0; i < mIndividualEnabled.size(); i++) {
int userId = mIndividualEnabled.keyAt(i);
SparseBooleanArray userIndividualEnabled =
mIndividualEnabled.valueAt(i);
for (int j = 0; j < userIndividualEnabled.size(); j++) {
int sensor = userIndividualEnabled.keyAt(j);
boolean enabled = userIndividualEnabled.valueAt(j);
setUserRestriction(userId, sensor, enabled);
}
}
}
int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE,
OP_CAMERA, OP_PHONE_CALL_CAMERA};
mAppOpsManager.startWatchingNoted(micAndCameraOps, this);
mAppOpsManager.startWatchingStarted(micAndCameraOps, this);
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
setIndividualSensorPrivacy(
((UserHandle) intent.getParcelableExtra(
Intent.EXTRA_USER)).getIdentifier(),
intent.getIntExtra(EXTRA_SENSOR, UNKNOWN), false);
}
}, new IntentFilter(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY),
MANAGE_SENSOR_PRIVACY, null);
mUserManagerInternal.addUserRestrictionsListener(this);
}
@Override
public void onUserRestrictionsChanged(int userId, Bundle newRestrictions,
Bundle prevRestrictions) {
// Reset sensor privacy when restriction is added
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_CAMERA_TOGGLE)) {
setIndividualSensorPrivacyUnchecked(userId, CAMERA, false);
}
if (!prevRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)
&& newRestrictions.getBoolean(UserManager.DISALLOW_MICROPHONE_TOGGLE)) {
setIndividualSensorPrivacyUnchecked(userId, MICROPHONE, false);
}
}
@Override
public void onOpStarted(int code, int uid, String packageName, String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) {
onOpNoted(code, uid, packageName, attributionTag, flags, result);
}
@Override
public void onOpNoted(int code, int uid, String packageName,
String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.Mode int result) {
if ((flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) {
return;
}
int sensor;
if (result == MODE_IGNORED) {
if (code == OP_RECORD_AUDIO) {
sensor = MICROPHONE;
} else if (code == OP_CAMERA) {
sensor = CAMERA;
} else {
return;
}
} else if (result == MODE_ALLOWED) {
if (code == OP_PHONE_CALL_MICROPHONE) {
sensor = MICROPHONE;
} else if (code == OP_PHONE_CALL_CAMERA) {
sensor = CAMERA;
} else {
return;
}
} else {
return;
}
long token = Binder.clearCallingIdentity();
try {
onSensorUseStarted(uid, packageName, sensor);
} finally {
Binder.restoreCallingIdentity(token);
}
}
/**
* Called when a sensor protected by individual sensor privacy is attempting to get used.
*
* @param uid The uid of the app using the sensor
* @param packageName The package name of the app using the sensor
* @param sensor The sensor that is attempting to be used
*/
private void onSensorUseStarted(int uid, String packageName, int sensor) {
UserHandle user = UserHandle.getUserHandleForUid(uid);
if (!isIndividualSensorPrivacyEnabled(user.getIdentifier(), sensor)) {
return;
}
synchronized (mLock) {
if (mSuppressReminders.containsKey(new Pair<>(packageName, user))) {
Log.d(TAG,
"Suppressed sensor privacy reminder for " + packageName + "/" + user);
return;
}
}
if (uid == Process.SYSTEM_UID) {
enqueueSensorUseReminderDialogAsync(-1, user, packageName, sensor);
return;
}
// TODO: Handle reminders with multiple sensors
// - If we have a likely activity that triggered the sensor use overlay a dialog over
// it. This should be the most common case.
// - If there is no use visible entity that triggered the sensor don't show anything as
// this is - from the point of the user - a background usage
// - Otherwise show a notification as we are not quite sure where to display the dialog.
List<RunningTaskInfo> tasksOfPackageUsingSensor = new ArrayList<>();
List<RunningTaskInfo> tasks = mActivityTaskManager.getTasks(Integer.MAX_VALUE);
int numTasks = tasks.size();
for (int taskNum = 0; taskNum < numTasks; taskNum++) {
RunningTaskInfo task = tasks.get(taskNum);
if (task.isVisible && task.topActivity.getPackageName().equals(packageName)) {
if (task.isFocused) {
// There is the one focused activity
enqueueSensorUseReminderDialogAsync(task.taskId, user, packageName, sensor);
return;
}
tasksOfPackageUsingSensor.add(task);
}
}
// TODO: Test this case
// There is one or more non-focused activity
if (tasksOfPackageUsingSensor.size() == 1) {
enqueueSensorUseReminderDialogAsync(tasksOfPackageUsingSensor.get(0).taskId, user,
packageName, sensor);
return;
} else if (tasksOfPackageUsingSensor.size() > 1) {
showSensorUseReminderNotification(user, packageName, sensor);
return;
}
// TODO: Test this case
// Check if there is a foreground service for this package
List<RunningServiceInfo> services = mActivityManager.getRunningServices(
Integer.MAX_VALUE);
int numServices = services.size();
for (int serviceNum = 0; serviceNum < numServices; serviceNum++) {
RunningServiceInfo service = services.get(serviceNum);
if (service.foreground && service.service.getPackageName().equals(packageName)) {
showSensorUseReminderNotification(user, packageName, sensor);
return;
}
}
VoiceInteractionManagerInternal voiceInteractionManagerInternal =
LocalServices.getService(VoiceInteractionManagerInternal.class);
if (sensor == MICROPHONE && voiceInteractionManagerInternal != null
&& voiceInteractionManagerInternal.hasActiveSession(packageName)) {
enqueueSensorUseReminderDialogAsync(-1, user, packageName, sensor);
return;
}
Log.i(TAG, packageName + "/" + uid + " started using sensor " + sensor
+ " but no activity or foreground service was running. The user will not be"
+ " informed. System components should check if sensor privacy is enabled for"
+ " the sensor before accessing it.");
}
/**
* Show a dialog that informs the user that a sensor use or a blocked sensor started.
* The user can then react to this event.
*
* @param taskId The task this dialog should be overlaid on.
* @param user The user of the package using the sensor.
* @param packageName The name of the package using the sensor.
* @param sensor The sensor that is being used.
*/
private void enqueueSensorUseReminderDialogAsync(int taskId, @NonNull UserHandle user,
@NonNull String packageName, int sensor) {
mHandler.sendMessage(PooledLambda.obtainMessage(
this:: enqueueSensorUseReminderDialog, taskId, user, packageName, sensor));
}
private void enqueueSensorUseReminderDialog(int taskId, @NonNull UserHandle user,
@NonNull String packageName, int sensor) {
SensorUseReminderDialogInfo info =
new SensorUseReminderDialogInfo(taskId, user, packageName);
if (!mQueuedSensorUseReminderDialogs.containsKey(info)) {
ArraySet<Integer> sensors = new ArraySet<Integer>();
sensors.add(sensor);
mQueuedSensorUseReminderDialogs.put(info, sensors);
mHandler.sendMessageDelayed(
PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info),
REMINDER_DIALOG_DELAY_MILLIS);
return;
}
ArraySet<Integer> sensors = mQueuedSensorUseReminderDialogs.get(info);
sensors.add(sensor);
}
private void showSensorUserReminderDialog(@NonNull SensorUseReminderDialogInfo info) {
ArraySet<Integer> sensors = mQueuedSensorUseReminderDialogs.get(info);
mQueuedSensorUseReminderDialogs.remove(info);
if (sensors == null) {
Log.e(TAG, "Unable to show sensor use dialog because sensor set is null."
+ " Was the dialog queue modified from outside the handler thread?");
return;
}
Intent dialogIntent = new Intent();
dialogIntent.setComponent(ComponentName.unflattenFromString(
mContext.getResources().getString(
R.string.config_sensorUseStartedActivity)));
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchTaskId(info.mTaskId);
options.setTaskOverlay(true, true);
dialogIntent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
dialogIntent.putExtra(EXTRA_PACKAGE_NAME, info.mPackageName);
if (sensors.size() == 1) {
dialogIntent.putExtra(EXTRA_SENSOR, sensors.valueAt(0));
} else if (sensors.size() == 2) {
dialogIntent.putExtra(EXTRA_ALL_SENSORS, true);
} else {
// Currently the only cases can be 1 or two
Log.e(TAG, "Attempted to show sensor use dialog for " + sensors.size()
+ " sensors");
return;
}
mContext.startActivityAsUser(dialogIntent, options.toBundle(), info.mUser);
}
/**
* Show a notification that informs the user that a sensor use or a blocked sensor started.
* The user can then react to this event.
*
* @param user The user of the package using the sensor.
* @param packageName The name of the package using the sensor.
* @param sensor The sensor that is being used.
*/
private void showSensorUseReminderNotification(@NonNull UserHandle user,
@NonNull String packageName, int sensor) {
int iconRes;
int messageRes;
CharSequence packageLabel;
try {
packageLabel = getUiContext().getPackageManager()
.getApplicationInfoAsUser(packageName, 0, user)
.loadLabel(mContext.getPackageManager());
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "Cannot show sensor use notification for " + packageName);
return;
}
if (sensor == MICROPHONE) {
iconRes = R.drawable.ic_mic_blocked;
messageRes = R.string.sensor_privacy_start_use_mic_notification_content_title;
} else {
iconRes = R.drawable.ic_camera_blocked;
messageRes = R.string.sensor_privacy_start_use_camera_notification_content_title;
}
NotificationManager notificationManager =
mContext.getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel(
SENSOR_PRIVACY_CHANNEL_ID,
getUiContext().getString(R.string.sensor_privacy_notification_channel_label),
NotificationManager.IMPORTANCE_HIGH);
channel.setSound(null, null);
channel.setBypassDnd(true);
channel.enableVibration(false);
channel.setBlockable(false);
notificationManager.createNotificationChannel(channel);
Icon icon = Icon.createWithResource(getUiContext().getResources(), iconRes);
notificationManager.notify(sensor,
new Notification.Builder(mContext, SENSOR_PRIVACY_CHANNEL_ID)
.setContentTitle(getUiContext().getString(messageRes))
.setContentText(Html.fromHtml(getUiContext().getString(
R.string.sensor_privacy_start_use_notification_content_text,
packageLabel),0))
.setSmallIcon(icon)
.addAction(new Notification.Action.Builder(icon,
getUiContext().getString(
R.string.sensor_privacy_start_use_dialog_turn_on_button),
PendingIntent.getBroadcast(mContext, sensor,
new Intent(ACTION_DISABLE_INDIVIDUAL_SENSOR_PRIVACY)
.setPackage(mContext.getPackageName())
.putExtra(EXTRA_SENSOR, sensor)
.putExtra(Intent.EXTRA_USER, user),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT))
.build())
.setContentIntent(PendingIntent.getActivity(mContext, sensor,
new Intent(Settings.ACTION_PRIVACY_SETTINGS),
PendingIntent.FLAG_IMMUTABLE
| PendingIntent.FLAG_UPDATE_CURRENT))
.build());
}
/**
* Sets the sensor privacy to the provided state and notifies all listeners of the new
* state.
*/
@Override
public void setSensorPrivacy(boolean enable) {
enforceManageSensorPrivacyPermission();
// Keep the state consistent between all users to make it a single global state
forAllUsers(userId -> setSensorPrivacy(userId, enable));
}
private void setSensorPrivacy(@UserIdInt int userId, boolean enable) {
synchronized (mLock) {
mEnabled.put(userId, enable);
persistSensorPrivacyStateLocked();
}
mHandler.onSensorPrivacyChanged(enable);
}
@Override
public void setIndividualSensorPrivacy(@UserIdInt int userId, int sensor, boolean enable) {
enforceManageSensorPrivacyPermission();
if (!canChangeIndividualSensorPrivacy(userId, sensor)) {
return;
}
setIndividualSensorPrivacyUnchecked(userId, sensor, enable);
}
private void setIndividualSensorPrivacyUnchecked(int userId, int sensor, boolean enable) {
synchronized (mLock) {
SparseBooleanArray userIndividualEnabled = mIndividualEnabled.get(userId,
new SparseBooleanArray());
userIndividualEnabled.put(sensor, enable);
mIndividualEnabled.put(userId, userIndividualEnabled);
if (!enable) {
long token = Binder.clearCallingIdentity();
try {
// Remove any notifications prompting the user to disable sensory privacy
NotificationManager notificationManager =
mContext.getSystemService(NotificationManager.class);
notificationManager.cancel(sensor);
} finally {
Binder.restoreCallingIdentity(token);
}
}
persistSensorPrivacyState();
}
mHandler.onSensorPrivacyChanged(userId, sensor, enable);
}
private boolean canChangeIndividualSensorPrivacy(@UserIdInt int userId, int sensor) {
if (sensor == MICROPHONE && mEmergencyCallHelper.isInEmergencyCall()) {
// During emergency call the microphone toggle managed automatically
Log.i(TAG, "Can't change mic toggle during an emergency call");
return false;
}
if (mKeyguardManager != null && mKeyguardManager.isDeviceLocked(userId)) {
Log.i(TAG, "Can't change mic/cam toggle while device is locked");
return false;
}
if (sensor == MICROPHONE && mUserManagerInternal.getUserRestriction(userId,
UserManager.DISALLOW_MICROPHONE_TOGGLE)) {
Log.i(TAG, "Can't change mic toggle due to admin restriction");
return false;
}
if (sensor == CAMERA && mUserManagerInternal.getUserRestriction(userId,
UserManager.DISALLOW_CAMERA_TOGGLE)) {
Log.i(TAG, "Can't change camera toggle due to admin restriction");
return false;
}
return true;
}
@Override
public void setIndividualSensorPrivacyForProfileGroup(@UserIdInt int userId, int sensor,
boolean enable) {
enforceManageSensorPrivacyPermission();
int parentId = mUserManagerInternal.getProfileParentId(userId);
forAllUsers(userId2 -> {
if (parentId == mUserManagerInternal.getProfileParentId(userId2)) {
setIndividualSensorPrivacy(userId2, sensor, enable);
}
});
}
/**
* Enforces the caller contains the necessary permission to change the state of sensor
* privacy.
*/
private void enforceManageSensorPrivacyPermission() {
enforcePermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY,
"Changing sensor privacy requires the following permission: "
+ MANAGE_SENSOR_PRIVACY);
}
/**
* Enforces the caller contains the necessary permission to observe changes to the sate of
* sensor privacy.
*/
private void enforceObserveSensorPrivacyPermission() {
enforcePermission(android.Manifest.permission.OBSERVE_SENSOR_PRIVACY,
"Observing sensor privacy changes requires the following permission: "
+ android.Manifest.permission.OBSERVE_SENSOR_PRIVACY);
}
private void enforcePermission(String permission, String message) {
if (mContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
return;
}
throw new SecurityException(message);
}
/**
* Returns whether sensor privacy is enabled.
*/
@Override
public boolean isSensorPrivacyEnabled() {
enforceObserveSensorPrivacyPermission();
return isSensorPrivacyEnabled(USER_SYSTEM);
}
private boolean isSensorPrivacyEnabled(@UserIdInt int userId) {
synchronized (mLock) {
return mEnabled.get(userId, false);
}
}
@Override
public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
enforceObserveSensorPrivacyPermission();
synchronized (mLock) {
SparseBooleanArray states = mIndividualEnabled.get(userId);
if (states == null) {
return false;
}
return states.get(sensor, false);
}
}
/**
* Returns the state of sensor privacy from persistent storage.
*/
private boolean readPersistedSensorPrivacyStateLocked() {
// if the file does not exist then sensor privacy has not yet been enabled on
// the device.
SparseArray<Object> map = new SparseArray<>();
int version = -1;
if (mAtomicFile.exists()) {
try (FileInputStream inputStream = mAtomicFile.openRead()) {
TypedXmlPullParser parser = Xml.resolvePullParser(inputStream);
XmlUtils.beginDocument(parser, XML_TAG_SENSOR_PRIVACY);
final int persistenceVersion = parser.getAttributeInt(null,
XML_ATTRIBUTE_PERSISTENCE_VERSION, 0);
// Use inline string literals for xml tags/attrs when parsing old versions since
// these should never be changed even with refactorings.
if (persistenceVersion == 0) {
boolean enabled = parser.getAttributeBoolean(null, "enabled", false);
SparseBooleanArray individualEnabled = new SparseBooleanArray();
version = 0;
XmlUtils.nextElement(parser);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if ("individual-sensor-privacy".equals(tagName)) {
int sensor = XmlUtils.readIntAttribute(parser, "sensor");
boolean indEnabled = XmlUtils.readBooleanAttribute(parser,
"enabled");
individualEnabled.put(sensor, indEnabled);
XmlUtils.skipCurrentTag(parser);
} else {
XmlUtils.nextElement(parser);
}
}
map.put(VER0_ENABLED, enabled);
map.put(VER0_INDIVIDUAL_ENABLED, individualEnabled);
} else if (persistenceVersion == CURRENT_PERSISTENCE_VERSION) {
SparseBooleanArray enabled = new SparseBooleanArray();
SparseArray<SparseBooleanArray> individualEnabled = new SparseArray<>();
version = parser.getAttributeInt(null,
XML_ATTRIBUTE_VERSION, 1);
int currentUserId = -1;
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
XmlUtils.nextElement(parser);
String tagName = parser.getName();
if (XML_TAG_USER.equals(tagName)) {
currentUserId = parser.getAttributeInt(null, XML_ATTRIBUTE_ID);
boolean isEnabled = parser.getAttributeBoolean(null,
XML_ATTRIBUTE_ENABLED);
if (enabled.indexOfKey(currentUserId) >= 0) {
Log.e(TAG, "User listed multiple times in file.",
new RuntimeException());
mAtomicFile.delete();
version = -1;
break;
}
if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
// User may no longer exist, skip this user
currentUserId = -1;
continue;
}
enabled.put(currentUserId, isEnabled);
}
if (XML_TAG_INDIVIDUAL_SENSOR_PRIVACY.equals(tagName)) {
if (mUserManagerInternal.getUserInfo(currentUserId) == null) {
// User may no longer exist or isn't set
continue;
}
int sensor = parser.getAttributeInt(null, XML_ATTRIBUTE_SENSOR);
boolean isEnabled = parser.getAttributeBoolean(null,
XML_ATTRIBUTE_ENABLED);
SparseBooleanArray userIndividualEnabled = individualEnabled.get(
currentUserId, new SparseBooleanArray());
userIndividualEnabled.put(sensor, isEnabled);
individualEnabled.put(currentUserId, userIndividualEnabled);
}
}
map.put(VER1_ENABLED, enabled);
map.put(VER1_INDIVIDUAL_ENABLED, individualEnabled);
} else {
Log.e(TAG, "Unknown persistence version: " + persistenceVersion
+ ". Deleting.",
new RuntimeException());
mAtomicFile.delete();
version = -1;
}
} 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();
version = -1;
}
}
return upgradeAndInit(version, map);
}
private boolean upgradeAndInit(int version, SparseArray map) {
if (version == -1) {
// New file, default state for current version goes here.
mEnabled = new SparseBooleanArray();
mIndividualEnabled = new SparseArray<>();
forAllUsers(userId -> mEnabled.put(userId, false));
forAllUsers(userId -> mIndividualEnabled.put(userId, new SparseBooleanArray()));
return true;
}
boolean upgraded = false;
final int[] users = getLocalService(UserManagerInternal.class).getUserIds();
if (version == 0) {
final boolean enabled = (boolean) map.get(VER0_ENABLED);
final SparseBooleanArray individualEnabled =
(SparseBooleanArray) map.get(VER0_INDIVIDUAL_ENABLED);
final SparseBooleanArray perUserEnabled = new SparseBooleanArray();
final SparseArray<SparseBooleanArray> perUserIndividualEnabled =
new SparseArray<>();
// Copy global state to each user
for (int i = 0; i < users.length; i++) {
int user = users[i];
perUserEnabled.put(user, enabled);
SparseBooleanArray userIndividualSensorEnabled = new SparseBooleanArray();
perUserIndividualEnabled.put(user, userIndividualSensorEnabled);
for (int j = 0; j < individualEnabled.size(); j++) {
final int sensor = individualEnabled.keyAt(j);
final boolean isSensorEnabled = individualEnabled.valueAt(j);
userIndividualSensorEnabled.put(sensor, isSensorEnabled);
}
}
map.clear();
map.put(VER1_ENABLED, perUserEnabled);
map.put(VER1_INDIVIDUAL_ENABLED, perUserIndividualEnabled);
version = 1;
upgraded = true;
}
if (version == CURRENT_VERSION) {
mEnabled = (SparseBooleanArray) map.get(VER1_ENABLED);
mIndividualEnabled =
(SparseArray<SparseBooleanArray>) map.get(VER1_INDIVIDUAL_ENABLED);
}
return upgraded;
}
/**
* Persists the state of sensor privacy.
*/
private void persistSensorPrivacyState() {
synchronized (mLock) {
persistSensorPrivacyStateLocked();
}
}
private void persistSensorPrivacyStateLocked() {
FileOutputStream outputStream = null;
try {
outputStream = mAtomicFile.startWrite();
TypedXmlSerializer serializer = Xml.resolveSerializer(outputStream);
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_SENSOR_PRIVACY);
serializer.attributeInt(
null, XML_ATTRIBUTE_PERSISTENCE_VERSION, CURRENT_PERSISTENCE_VERSION);
serializer.attributeInt(null, XML_ATTRIBUTE_VERSION, CURRENT_VERSION);
forAllUsers(userId -> {
serializer.startTag(null, XML_TAG_USER);
serializer.attributeInt(null, XML_ATTRIBUTE_ID, userId);
serializer.attributeBoolean(
null, XML_ATTRIBUTE_ENABLED, isSensorPrivacyEnabled(userId));
SparseBooleanArray individualEnabled =
mIndividualEnabled.get(userId, new SparseBooleanArray());
int numIndividual = individualEnabled.size();
for (int i = 0; i < numIndividual; i++) {
serializer.startTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
int sensor = individualEnabled.keyAt(i);
boolean enabled = individualEnabled.valueAt(i);
serializer.attributeInt(null, XML_ATTRIBUTE_SENSOR, sensor);
serializer.attributeBoolean(null, XML_ATTRIBUTE_ENABLED, enabled);
serializer.endTag(null, XML_TAG_INDIVIDUAL_SENSOR_PRIVACY);
}
serializer.endTag(null, XML_TAG_USER);
});
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);
}
}
@Override
public boolean supportsSensorToggle(int sensor) {
if (sensor == MICROPHONE) {
return mContext.getResources().getBoolean(R.bool.config_supportsMicToggle);
} else if (sensor == CAMERA) {
return mContext.getResources().getBoolean(R.bool.config_supportsCamToggle);
}
throw new IllegalArgumentException("Unable to find value " + sensor);
}
/**
* Registers a listener to be notified when the sensor privacy state changes.
*/
@Override
public void addSensorPrivacyListener(ISensorPrivacyListener listener) {
enforceObserveSensorPrivacyPermission();
if (listener == null) {
throw new NullPointerException("listener cannot be null");
}
mHandler.addListener(listener);
}
/**
* Registers a listener to be notified when the sensor privacy state changes.
*/
@Override
public void addIndividualSensorPrivacyListener(int userId, int sensor,
ISensorPrivacyListener listener) {
enforceObserveSensorPrivacyPermission();
if (listener == null) {
throw new NullPointerException("listener cannot be null");
}
mHandler.addListener(userId, sensor, listener);
}
/**
* Unregisters a listener from sensor privacy state change notifications.
*/
@Override
public void removeSensorPrivacyListener(ISensorPrivacyListener listener) {
enforceObserveSensorPrivacyPermission();
if (listener == null) {
throw new NullPointerException("listener cannot be null");
}
mHandler.removeListener(listener);
}
@Override
public void suppressIndividualSensorPrivacyReminders(int userId, String packageName,
IBinder token, boolean suppress) {
enforceManageSensorPrivacyPermission();
Objects.requireNonNull(packageName);
Objects.requireNonNull(token);
Pair<String, UserHandle> key = new Pair<>(packageName, UserHandle.of(userId));
synchronized (mLock) {
if (suppress) {
try {
token.linkToDeath(this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Could not suppress sensor use reminder", e);
return;
}
ArrayList<IBinder> suppressPackageReminderTokens = mSuppressReminders.get(key);
if (suppressPackageReminderTokens == null) {
suppressPackageReminderTokens = new ArrayList<>(1);
mSuppressReminders.put(key, suppressPackageReminderTokens);
}
suppressPackageReminderTokens.add(token);
} else {
mHandler.removeSuppressPackageReminderToken(key, token);
}
}
}
/**
* Remove a sensor use reminder suppression token.
*
* @param key Key the token is in
* @param token The token to remove
*/
private void removeSuppressPackageReminderToken(@NonNull Pair<String, UserHandle> key,
@NonNull IBinder token) {
synchronized (mLock) {
ArrayList<IBinder> suppressPackageReminderTokens =
mSuppressReminders.get(key);
if (suppressPackageReminderTokens == null) {
Log.e(TAG, "No tokens for " + key);
return;
}
boolean wasRemoved = suppressPackageReminderTokens.remove(token);
if (wasRemoved) {
token.unlinkToDeath(this, 0);
if (suppressPackageReminderTokens.isEmpty()) {
mSuppressReminders.remove(key);
}
} else {
Log.w(TAG, "Could not remove sensor use reminder suppression token " + token
+ " from " + key);
}
}
}
/**
* A owner of a suppressor token died. Clean up.
*
* @param token The token that is invalid now.
*/
@Override
public void binderDied(@NonNull IBinder token) {
synchronized (mLock) {
for (Pair<String, UserHandle> key : mSuppressReminders.keySet()) {
removeSuppressPackageReminderToken(key, token);
}
}
}
@Override
public void binderDied() {
// Handled in binderDied(IBinder)
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Objects.requireNonNull(fd);
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
int opti = 0;
boolean dumpAsProto = false;
while (opti < args.length) {
String opt = args[opti];
if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
break;
}
opti++;
if ("--proto".equals(opt)) {
dumpAsProto = true;
} else {
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
final long identity = Binder.clearCallingIdentity();
try {
if (dumpAsProto) {
dump(new DualDumpOutputStream(new ProtoOutputStream(fd)));
} else {
pw.println("SENSOR PRIVACY MANAGER STATE (dumpsys "
+ Context.SENSOR_PRIVACY_SERVICE + ")");
dump(new DualDumpOutputStream(new IndentingPrintWriter(pw, " ")));
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Dump state to {@link DualDumpOutputStream}.
*
* @param dumpStream The destination to dump to
*/
private void dump(@NonNull DualDumpOutputStream dumpStream) {
synchronized (mLock) {
forAllUsers(userId -> {
long userToken = dumpStream.start("users", SensorPrivacyServiceDumpProto.USER);
dumpStream.write("user_id", SensorPrivacyUserProto.USER_ID, userId);
dumpStream.write("is_enabled", SensorPrivacyUserProto.IS_ENABLED,
mEnabled.get(userId, false));
SparseBooleanArray individualEnabled = mIndividualEnabled.get(userId);
if (individualEnabled != null) {
int numIndividualEnabled = individualEnabled.size();
for (int i = 0; i < numIndividualEnabled; i++) {
long individualToken = dumpStream.start("individual_enabled_sensor",
SensorPrivacyUserProto.INDIVIDUAL_ENABLED_SENSOR);
dumpStream.write("sensor",
SensorPrivacyIndividualEnabledSensorProto.SENSOR,
individualEnabled.keyAt(i));
dumpStream.write("is_enabled",
SensorPrivacyIndividualEnabledSensorProto.IS_ENABLED,
individualEnabled.valueAt(i));
dumpStream.end(individualToken);
}
}
dumpStream.end(userToken);
});
}
dumpStream.flush();
}
/**
* Convert a string into a {@link SensorPrivacyManager.Sensors.Sensor id}.
*
* @param sensor The name to convert
*
* @return The id corresponding to the name
*/
private @SensorPrivacyManager.Sensors.Sensor int sensorStrToId(@Nullable String sensor) {
if (sensor == null) {
return UNKNOWN;
}
switch (sensor) {
case "microphone":
return MICROPHONE;
case "camera":
return CAMERA;
default: {
return UNKNOWN;
}
}
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
(new ShellCommand() {
@Override
public int onCommand(String cmd) {
if (cmd == null) {
return handleDefaultCommands(cmd);
}
int userId = Integer.parseInt(getNextArgRequired());
final PrintWriter pw = getOutPrintWriter();
switch (cmd) {
case "enable" : {
int sensor = sensorStrToId(getNextArgRequired());
if (sensor == UNKNOWN) {
pw.println("Invalid sensor");
return -1;
}
setIndividualSensorPrivacy(userId, sensor, true);
}
break;
case "disable" : {
int sensor = sensorStrToId(getNextArgRequired());
if (sensor == UNKNOWN) {
pw.println("Invalid sensor");
return -1;
}
setIndividualSensorPrivacy(userId, sensor, false);
}
break;
case "reset": {
int sensor = sensorStrToId(getNextArgRequired());
if (sensor == UNKNOWN) {
pw.println("Invalid sensor");
return -1;
}
enforceManageSensorPrivacyPermission();
synchronized (mLock) {
SparseBooleanArray individualEnabled =
mIndividualEnabled.get(userId);
if (individualEnabled != null) {
individualEnabled.delete(sensor);
}
persistSensorPrivacyState();
}
}
break;
default:
return handleDefaultCommands(cmd);
}
return 0;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Sensor privacy manager (" + Context.SENSOR_PRIVACY_SERVICE
+ ") commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
pw.println(" enable USER_ID SENSOR");
pw.println(" Enable privacy for a certain sensor.");
pw.println("");
pw.println(" disable USER_ID SENSOR");
pw.println(" Disable privacy for a certain sensor.");
pw.println("");
pw.println(" reset USER_ID SENSOR");
pw.println(" Reset privacy state for a certain sensor.");
pw.println("");
}
}).exec(this, in, out, err, args, callback, resultReceiver);
}
}
/**
* 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<>();
@GuardedBy("mListenerLock")
private final SparseArray<SparseArray<RemoteCallbackList<ISensorPrivacyListener>>>
mIndividualSensorListeners = new SparseArray<>();
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 onSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
sendMessage(PooledLambda.obtainMessage(SensorPrivacyHandler::handleSensorPrivacyChanged,
this, userId, sensor, 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 addListener(int userId, int sensor, ISensorPrivacyListener listener) {
synchronized (mListenerLock) {
DeathRecipient deathRecipient = new DeathRecipient(listener);
mDeathRecipients.put(listener, deathRecipient);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
if (listenersForUser == null) {
listenersForUser = new SparseArray<>();
mIndividualSensorListeners.put(userId, listenersForUser);
}
RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor);
if (listeners == null) {
listeners = new RemoteCallbackList<>();
listenersForUser.put(sensor, listeners);
}
listeners.register(listener);
}
}
public void removeListener(ISensorPrivacyListener listener) {
synchronized (mListenerLock) {
DeathRecipient deathRecipient = mDeathRecipients.remove(listener);
if (deathRecipient != null) {
deathRecipient.destroy();
}
mListeners.unregister(listener);
for (int i = 0, numUsers = mIndividualSensorListeners.size(); i < numUsers; i++) {
for (int j = 0, numListeners = mIndividualSensorListeners.valueAt(i).size();
j < numListeners; j++) {
mIndividualSensorListeners.valueAt(i).valueAt(j).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();
}
public void handleSensorPrivacyChanged(int userId, int sensor, boolean enabled) {
mSensorPrivacyManagerInternal.dispatch(userId, sensor, enabled);
SparseArray<RemoteCallbackList<ISensorPrivacyListener>> listenersForUser =
mIndividualSensorListeners.get(userId);
setUserRestriction(userId, sensor, enabled);
if (listenersForUser == null) {
return;
}
RemoteCallbackList<ISensorPrivacyListener> listeners = listenersForUser.get(sensor);
if (listeners == null) {
return;
}
final int count = listeners.beginBroadcast();
for (int i = 0; i < count; i++) {
ISensorPrivacyListener listener = listeners.getBroadcastItem(i);
try {
listener.onSensorPrivacyChanged(enabled);
} catch (RemoteException e) {
Log.e(TAG, "Caught an exception notifying listener " + listener + ": ", e);
}
}
listeners.finishBroadcast();
}
public void removeSuppressPackageReminderToken(Pair<String, UserHandle> key,
IBinder token) {
sendMessage(PooledLambda.obtainMessage(
SensorPrivacyServiceImpl::removeSuppressPackageReminderToken,
mSensorPrivacyServiceImpl, key, token));
}
}
private void setUserRestriction(int userId, int sensor, boolean enabled) {
if (sensor == CAMERA) {
mAppOpsManager.setUserRestrictionForUser(OP_CAMERA, enabled,
mAppOpsRestrictionToken, null, userId);
} else if (sensor == MICROPHONE) {
mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO, enabled,
mAppOpsRestrictionToken, null, userId);
mAppOpsManager.setUserRestrictionForUser(OP_RECORD_AUDIO_HOTWORD, enabled,
mAppOpsRestrictionToken, null, userId);
}
}
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) {
}
}
}
private void forAllUsers(FunctionalUtils.ThrowingConsumer<Integer> c) {
int[] userIds = mUserManagerInternal.getUserIds();
for (int i = 0; i < userIds.length; i++) {
c.accept(userIds[i]);
}
}
private class SensorPrivacyManagerInternalImpl extends SensorPrivacyManagerInternal {
private ArrayMap<Integer, ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>>>
mListeners = new ArrayMap<>();
private ArrayMap<Integer, ArraySet<OnUserSensorPrivacyChangedListener>> mAllUserListeners =
new ArrayMap<>();
private final Object mLock = new Object();
private void dispatch(int userId, int sensor, boolean enabled) {
synchronized (mLock) {
ArraySet<OnUserSensorPrivacyChangedListener> allUserSensorListeners =
mAllUserListeners.get(sensor);
if (allUserSensorListeners != null) {
for (int i = 0; i < allUserSensorListeners.size(); i++) {
OnUserSensorPrivacyChangedListener listener =
allUserSensorListeners.valueAt(i);
BackgroundThread.getHandler().post(() ->
listener.onSensorPrivacyChanged(userId, enabled));
}
}
ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>> userSensorListeners =
mListeners.get(userId);
if (userSensorListeners != null) {
ArraySet<OnSensorPrivacyChangedListener> sensorListeners =
userSensorListeners.get(sensor);
if (sensorListeners != null) {
for (int i = 0; i < sensorListeners.size(); i++) {
OnSensorPrivacyChangedListener listener = sensorListeners.valueAt(i);
BackgroundThread.getHandler().post(() ->
listener.onSensorPrivacyChanged(enabled));
}
}
}
}
}
@Override
public boolean isSensorPrivacyEnabled(int userId, int sensor) {
return SensorPrivacyService.this
.mSensorPrivacyServiceImpl.isIndividualSensorPrivacyEnabled(userId, sensor);
}
@Override
public void addSensorPrivacyListener(int userId, int sensor,
OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
ArrayMap<Integer, ArraySet<OnSensorPrivacyChangedListener>> userSensorListeners =
mListeners.get(userId);
if (userSensorListeners == null) {
userSensorListeners = new ArrayMap<>();
mListeners.put(userId, userSensorListeners);
}
ArraySet<OnSensorPrivacyChangedListener> sensorListeners =
userSensorListeners.get(sensor);
if (sensorListeners == null) {
sensorListeners = new ArraySet<>();
userSensorListeners.put(sensor, sensorListeners);
}
sensorListeners.add(listener);
}
}
@Override
public void addSensorPrivacyListenerForAllUsers(int sensor,
OnUserSensorPrivacyChangedListener listener) {
synchronized (mLock) {
ArraySet<OnUserSensorPrivacyChangedListener> sensorListeners =
mAllUserListeners.get(sensor);
if (sensorListeners == null) {
sensorListeners = new ArraySet<>();
mAllUserListeners.put(sensor, sensorListeners);
}
sensorListeners.add(listener);
}
}
}
private class EmergencyCallHelper {
private OutogingEmergencyStateCallback mEmergencyStateCallback;
private CallStateCallback mCallStateCallback;
private boolean mIsInEmergencyCall;
private boolean mMicUnmutedForEmergencyCall;
private Object mEmergencyStateLock = new Object();
EmergencyCallHelper() {
mEmergencyStateCallback = new OutogingEmergencyStateCallback();
mCallStateCallback = new CallStateCallback();
mTelephonyManager.registerTelephonyCallback(FgThread.getExecutor(),
mEmergencyStateCallback);
mTelephonyManager.registerTelephonyCallback(FgThread.getExecutor(),
mCallStateCallback);
}
boolean isInEmergencyCall() {
synchronized (mEmergencyStateLock) {
return mIsInEmergencyCall;
}
}
private class OutogingEmergencyStateCallback extends TelephonyCallback implements
TelephonyCallback.OutgoingEmergencyCallListener {
@Override
public void onOutgoingEmergencyCall(EmergencyNumber placedEmergencyNumber,
int subscriptionId) {
onEmergencyCall();
}
}
private class CallStateCallback extends TelephonyCallback implements
TelephonyCallback.CallStateListener {
@Override
public void onCallStateChanged(int state) {
if (state == TelephonyManager.CALL_STATE_IDLE) {
onCallOver();
}
}
}
private void onEmergencyCall() {
synchronized (mEmergencyStateLock) {
if (!mIsInEmergencyCall) {
mIsInEmergencyCall = true;
if (mSensorPrivacyServiceImpl
.isIndividualSensorPrivacyEnabled(getCurrentUser(), MICROPHONE)) {
mSensorPrivacyServiceImpl.setIndividualSensorPrivacyUnchecked(
getCurrentUser(), MICROPHONE, false);
mMicUnmutedForEmergencyCall = true;
} else {
mMicUnmutedForEmergencyCall = false;
}
}
}
}
private void onCallOver() {
synchronized (mEmergencyStateLock) {
if (mIsInEmergencyCall) {
mIsInEmergencyCall = false;
if (mMicUnmutedForEmergencyCall) {
mSensorPrivacyServiceImpl.setIndividualSensorPrivacyUnchecked(
getCurrentUser(), MICROPHONE, true);
mMicUnmutedForEmergencyCall = false;
}
}
}
}
}
}