diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java index a05eb6818b17a..ca59ce3acac78 100644 --- a/services/core/java/com/android/server/SensorPrivacyService.java +++ b/services/core/java/com/android/server/SensorPrivacyService.java @@ -20,12 +20,17 @@ 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; @@ -60,6 +65,7 @@ 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; @@ -141,6 +147,7 @@ public final class SensorPrivacyService extends SystemService { 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; @@ -206,6 +213,36 @@ public final class SensorPrivacyService extends SystemService { private ArrayMap, ArrayList> mSuppressReminders = new ArrayMap<>(); + private final ArrayMap> + 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(), @@ -228,7 +265,8 @@ public final class SensorPrivacyService extends SystemService { } } - int[] micAndCameraOps = new int[]{OP_RECORD_AUDIO, OP_CAMERA}; + 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); @@ -254,15 +292,29 @@ public final class SensorPrivacyService extends SystemService { public void onOpNoted(int code, int uid, String packageName, String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.Mode int result) { - if (result != MODE_IGNORED || (flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) { + if ((flags & AppOpsManager.OP_FLAGS_ALL_TRUSTED) == 0) { return; } int sensor; - if (code == OP_RECORD_AUDIO) { - sensor = MICROPHONE; + 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 { - sensor = CAMERA; + return; } long token = Binder.clearCallingIdentity(); @@ -294,6 +346,11 @@ public final class SensorPrivacyService extends SystemService { } } + 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 @@ -312,7 +369,7 @@ public final class SensorPrivacyService extends SystemService { if (task.isVisible && task.topActivity.getPackageName().equals(packageName)) { if (task.isFocused) { // There is the one focused activity - showSensorUseReminderDialog(task.taskId, user, packageName, sensor); + enqueueSensorUseReminderDialogAsync(task.taskId, user, packageName, sensor); return; } @@ -323,7 +380,7 @@ public final class SensorPrivacyService extends SystemService { // TODO: Test this case // There is one or more non-focused activity if (tasksOfPackageUsingSensor.size() == 1) { - showSensorUseReminderDialog(tasksOfPackageUsingSensor.get(0).taskId, user, + enqueueSensorUseReminderDialogAsync(tasksOfPackageUsingSensor.get(0).taskId, user, packageName, sensor); return; } else if (tasksOfPackageUsingSensor.size() > 1) { @@ -360,21 +417,60 @@ public final class SensorPrivacyService extends SystemService { * @param packageName The name of the package using the sensor. * @param sensor The sensor that is being used. */ - private void showSensorUseReminderDialog(int taskId, @NonNull UserHandle user, + 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 sensors = new ArraySet(); + sensors.add(sensor); + mQueuedSensorUseReminderDialogs.put(info, sensors); + mHandler.sendMessageDelayed( + PooledLambda.obtainMessage(this::showSensorUserReminderDialog, info), + REMINDER_DIALOG_DELAY_MILLIS); + return; + } + ArraySet sensors = mQueuedSensorUseReminderDialogs.get(info); + sensors.add(sensor); + } + + private void showSensorUserReminderDialog(@NonNull SensorUseReminderDialogInfo info) { + ArraySet 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(taskId); + options.setLaunchTaskId(info.mTaskId); options.setTaskOverlay(true, true); - dialogIntent.putExtra(EXTRA_PACKAGE_NAME, packageName); - dialogIntent.putExtra(EXTRA_SENSOR, sensor); + dialogIntent.addFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - mContext.startActivityAsUser(dialogIntent, options.toBundle(), user); + 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); } /**