Merge "Add new TV bottom sheets for HTT mic mute"

This commit is contained in:
Iavor-Valentin Iftime
2022-11-03 12:32:35 +00:00
committed by Android (Google) Code Review
11 changed files with 416 additions and 5 deletions

View File

@@ -72,6 +72,13 @@ public final class SensorPrivacyManager {
public static final String EXTRA_ALL_SENSORS = SensorPrivacyManager.class.getName()
+ ".extra.all_sensors";
/**
* An extra containing the sensor type
* @hide
*/
public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+ ".extra.toggle_type";
/**
* Sensor constants which are used in {@link SensorPrivacyManager}
*/

View File

@@ -54,6 +54,12 @@
com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
</string>
<!-- Component name of the activity used to inform a user about a sensor privacy update from
SW/HW privacy switches. -->
<string name="config_sensorStateChangedActivity" translatable="false">
com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
</string>
<!-- Component name of the activity that shows the request for access to a usb device. -->
<string name="config_usbPermissionActivity" translatable="false">
com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity

View File

@@ -2944,6 +2944,9 @@
<string name="config_usbResolverActivity" translatable="false"
>com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
<!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
<string name="config_sensorStateChangedActivity" translatable="false"></string>
<!-- Component name of the activity used to inform a user about a sensory being blocked because
of privacy settings. -->
<string name="config_sensorUseStartedActivity" translatable="false"

View File

@@ -374,6 +374,7 @@
<java-symbol type="string" name="config_usbResolverActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity" />
<java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
<java-symbol type="string" name="config_sensorStateChangedActivity" />
<java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
<java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
<java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />

View File

@@ -557,6 +557,16 @@
android:showForAllUsers="true">
</activity>
<!-- started from SensoryPrivacyService -->
<activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
android:exported="true"
android:launchMode="singleTop"
android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
android:theme="@style/BottomSheet"
android:finishOnCloseSystemDialogs="true"
android:showForAllUsers="true">
</activity>
<!-- started from UsbDeviceSettingsManager -->
<activity android:name=".usb.UsbAccessoryUriActivity"

View File

@@ -783,6 +783,62 @@
Microphone and camera available
</string>
<!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_turned_on_dialog_title">
Microphone turned on
</string>
<!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_turned_off_dialog_title">
Microphone turned off
</string>
<!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_unblocked_dialog_content">
Microphone is enabled for all apps and services.
</string>
<!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
Microphone access is disabled for all apps and services.
You can enable microphone access in Settings &gt; Privacy &gt; Microphone.
</string>
<!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
Microphone access is disabled for all apps and services.
You can change this in Settings &gt; Privacy &gt; Microphone.
</string>
<!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_camera_turned_on_dialog_title">
Camera turned on
</string>
<!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_camera_turned_off_dialog_title">
Camera turned off
</string>
<!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_camera_unblocked_dialog_content">
Camera is enabled for all apps and services.
</string>
<!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_camera_blocked_dialog_content">
Camera access is disabled for all apps and services.
</string>
<!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
<!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
<string name="sensor_privacy_dialog_open_settings">Open settings.</string>
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
<string name="media_seamless_other_device">Other device</string>

View File

@@ -25,6 +25,7 @@ import com.android.systemui.people.PeopleSpaceActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.screenshot.LongScreenshotActivity;
import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
import com.android.systemui.settings.brightness.BrightnessDialog;
import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@ public abstract class DefaultActivityBinder {
@ClassKey(HdmiCecSetMenuLanguageActivity.class)
public abstract Activity bindHdmiCecSetMenuLanguageActivity(
HdmiCecSetMenuLanguageActivity activity);
/** Inject into TvSensorPrivacyChangedActivity. */
@Binds
@IntoMap
@ClassKey(TvSensorPrivacyChangedActivity.class)
public abstract Activity bindTvSensorPrivacyChangedActivity(
TvSensorPrivacyChangedActivity activity);
}

View File

@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
import android.app.INotificationManager;
@@ -135,6 +136,12 @@ public class FrameworkServicesModule {
return new AmbientDisplayConfiguration(context);
}
@Provides
@Singleton
static AppOpsManager provideAppOpsManager(Context context) {
return context.getSystemService(AppOpsManager.class);
}
@Provides
@Singleton
static AudioManager provideAudioManager(Context context) {

View File

@@ -0,0 +1,233 @@
/*
* Copyright (C) 2022 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.systemui.sensorprivacy.television;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import android.annotation.DimenRes;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.tv.TvBottomSheetActivity;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
/**
* Bottom sheet that is shown when the camera/mic sensors privacy state changed
* by the global software toggle or physical privacy switch.
*/
public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
private static final int ALL_SENSORS = Integer.MAX_VALUE;
private int mSensor = -1;
private int mToggleType = -1;
private final GlobalSettings mGlobalSettings;
private final IndividualSensorPrivacyController mSensorPrivacyController;
private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
private TextView mTitle;
private TextView mContent;
private ImageView mIcon;
private ImageView mSecondIcon;
private Button mPositiveButton;
private Button mCancelButton;
@Inject
public TvSensorPrivacyChangedActivity(
IndividualSensorPrivacyController individualSensorPrivacyController,
GlobalSettings globalSettings) {
mSensorPrivacyController = individualSensorPrivacyController;
mGlobalSettings = globalSettings;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addSystemFlags(
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
false);
if (allSensors) {
mSensor = ALL_SENSORS;
} else {
mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
}
mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
if (mSensor == -1 || mToggleType == -1) {
Log.v(TAG, "Invalid extras");
finish();
return;
}
// Do not show for software toggles
if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
finish();
return;
}
mSensorPrivacyCallback = (sensor, blocked) -> {
updateUI();
};
initUI();
}
private void initUI() {
mTitle = findViewById(R.id.bottom_sheet_title);
mContent = findViewById(R.id.bottom_sheet_body);
mIcon = findViewById(R.id.bottom_sheet_icon);
// mic icon if both icons are shown
mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
mCancelButton.setText(android.R.string.cancel);
mCancelButton.setOnClickListener(v -> finish());
updateUI();
}
private void updateUI() {
final Resources resources = getResources();
setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
switch (mSensor) {
case CAMERA:
updateUiForCameraUpdate(
mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
break;
case MICROPHONE:
default:
updateUiForMicUpdate(
mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
break;
}
// Start animation if drawable is animated
Drawable iconDrawable = mIcon.getDrawable();
if (iconDrawable instanceof Animatable) {
((Animatable) iconDrawable).start();
}
mPositiveButton.setVisibility(View.GONE);
mCancelButton.setText(android.R.string.ok);
}
private void updateUiForMicUpdate(boolean blocked) {
if (blocked) {
mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
if (isExplicitUserInteractionAudioBypassAllowed()) {
mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
} else {
mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
}
mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
mSecondIcon.setVisibility(View.GONE);
} else {
mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
mSecondIcon.setVisibility(View.GONE);
}
}
private void updateUiForCameraUpdate(boolean blocked) {
if (blocked) {
mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
mSecondIcon.setVisibility(View.GONE);
} else {
mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
mSecondIcon.setVisibility(View.GONE);
}
}
private void setIconTint(boolean enableTint) {
final Resources resources = getResources();
if (enableTint) {
final ColorStateList iconTint = resources.getColorStateList(
R.color.bottom_sheet_icon_color, getTheme());
mIcon.setImageTintList(iconTint);
mSecondIcon.setImageTintList(iconTint);
} else {
mIcon.setImageTintList(null);
mSecondIcon.setImageTintList(null);
}
mIcon.invalidate();
mSecondIcon.invalidate();
}
private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
final Resources resources = getResources();
final int iconWidth = resources.getDimensionPixelSize(widthRes);
final int iconHeight = resources.getDimensionPixelSize(heightRes);
mIcon.getLayoutParams().width = iconWidth;
mIcon.getLayoutParams().height = iconHeight;
mIcon.invalidate();
mSecondIcon.getLayoutParams().width = iconWidth;
mSecondIcon.getLayoutParams().height = iconHeight;
mSecondIcon.invalidate();
}
private boolean isExplicitUserInteractionAudioBypassAllowed() {
return mGlobalSettings.getInt(
Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
}
@Override
public void onResume() {
super.onResume();
updateUI();
mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
}
@Override
public void onPause() {
mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
super.onPause();
}
}

View File

@@ -16,17 +16,23 @@
package com.android.systemui.sensorprivacy.television;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.OTHER;
import android.annotation.DimenRes;
import android.app.AppOpsManager;
import android.app.role.RoleManager;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
@@ -39,6 +45,8 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.tv.TvBottomSheetActivity;
import java.util.List;
import javax.inject.Inject;
/**
@@ -57,6 +65,8 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
private int mSensor = -1;
private final AppOpsManager mAppOpsManager;
private final RoleManager mRoleManager;
private final IndividualSensorPrivacyController mSensorPrivacyController;
private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
private TextView mTitle;
@@ -68,8 +78,11 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
@Inject
public TvUnblockSensorActivity(
IndividualSensorPrivacyController individualSensorPrivacyController) {
IndividualSensorPrivacyController individualSensorPrivacyController,
AppOpsManager appOpsManager, RoleManager roleManager) {
mSensorPrivacyController = individualSensorPrivacyController;
mAppOpsManager = appOpsManager;
mRoleManager = roleManager;
}
@Override
@@ -120,6 +133,10 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
break;
}
showToastAndFinish(toastMsgResId);
}
private void showToastAndFinish(int toastMsgResId) {
Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
finish();
}
@@ -149,7 +166,9 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
}
private void updateUI() {
if (isBlockedByHardwareToggle()) {
if (isHTTAccessDisabled()) {
updateUiForHTT();
} else if (isBlockedByHardwareToggle()) {
updateUiForHardwareToggle();
} else {
updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
switch (mSensor) {
case MICROPHONE:
mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
mSecondIcon.setVisibility(View.GONE);
break;
case CAMERA:
mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setVisibility(View.GONE);
break;
case ALL_SENSORS:
default:
mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
});
}
private void updateUiForHTT() {
setIconTint(true);
setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
mSecondIcon.setVisibility(View.GONE);
mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
mPositiveButton.setOnClickListener(v -> {
Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
MATCH_SYSTEM_ONLY);
if (activityInfo == null) {
showToastAndFinish(com.android.internal.R.string.noApplications);
} else {
startActivity(openPrivacySettings);
finish();
}
});
}
private void setIconTint(boolean enableTint) {
final Resources resources = getResources();
@@ -272,6 +314,18 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
mSecondIcon.invalidate();
}
private boolean isHTTAccessDisabled() {
String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
if (!assistantPkgs.contains(pkg)) {
return false;
}
return (mAppOpsManager.checkOpNoThrow(
AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
pkg) != AppOpsManager.MODE_ALLOWED);
}
@Override
public void onResume() {
super.onResume();

View File

@@ -35,6 +35,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
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.EXTRA_TOGGLE_TYPE;
import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@ public final class SensorPrivacyService extends SystemService {
.build());
}
private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
@SensorPrivacyManager.ToggleType int toggleType) {
String activityName = mContext.getResources().getString(
R.string.config_sensorStateChangedActivity);
if (TextUtils.isEmpty(activityName)) {
return;
}
Intent dialogIntent = new Intent();
dialogIntent.setComponent(
ComponentName.unflattenFromString(activityName));
ActivityOptions options = ActivityOptions.makeBasic();
options.setTaskOverlay(true, true);
dialogIntent.addFlags(
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
dialogIntent.putExtra(EXTRA_SENSOR, sensor);
dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
}
private boolean isTelevision(Context context) {
int uiMode = context.getResources().getConfiguration().uiMode;
return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@ public final class SensorPrivacyService extends SystemService {
mToggleSensorListeners.finishBroadcast();
}
}
mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
}
public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,