Add mic sensor privacy QS tile

- factor out generic code from CameraToggleTile and
- reuse this generic code for MicrophoneToggleTile

Bug: 162549680
Test: Toggled tiles and looked at `dumpsys sensor_privacy`
Change-Id: Ib45c54c686e271facb78f0f14861045cb97641bf
This commit is contained in:
Philip P. Moltmann
2021-01-05 18:12:52 -08:00
parent bde12fd465
commit eca12d241c
11 changed files with 279 additions and 72 deletions

View File

@@ -0,0 +1,29 @@
<!--
~ Copyright (C) 2021 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="m17,12c-2.75,0 -5,2.25 -5,5 0,2.75 2.25,5 5,5 2.75,0 5,-2.25 5,-5 0,-2.75 -2.1667,-5 -5,-5zM14.5,17.8333h5v-1.6666h-5z"
android:fillColor="#30302a"
android:fillType="evenOdd"/>
<path
android:pathData="m12,12c0,1.66 -1.34,3 -3,3C7.34,15 6,13.66 6,12L6,6C6,4.34 7.34,3 9,3c1.66,0 3,1.34 3,3zM9,5C8.45,5 8,5.45 8,6v6c0,0.55 0.45,1 1,1 0.55,0 1,-0.45 1,-1L10,6C10,5.45 9.55,5 9,5ZM11.0147,16.577C10.3983,16.849 9.7167,17 9,17 6.24,17 4,14.76 4,12L2,12c0,3.53 2.61,6.43 6,6.92L8,22h2v-3.08c0.4212,-0.0609 0.8303,-0.1589 1.2238,-0.2908C11.078,18.1111 11,17.5647 11,17c0,-0.1422 0.0049,-0.2832 0.0147,-0.423z"
android:fillColor="#30302a"
android:fillType="evenOdd"/>
</vector>

View File

@@ -107,7 +107,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle
wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,screenrecord,reverse,reduce_brightness,cameratoggle,mictoggle
</string>
<!-- The tiles to display in QuickSettings -->

View File

@@ -823,6 +823,8 @@
<string name="quick_settings_location_off_label">Location Off</string>
<!-- QuickSettings: Camera [CHAR LIMIT=NONE] -->
<string name="quick_settings_camera_label">Block Camera</string>
<!-- QuickSettings: Microphone [CHAR LIMIT=NONE] -->
<string name="quick_settings_mic_label">Mute Microphone</string>
<!-- QuickSettings: Media device [CHAR LIMIT=NONE] -->
<string name="quick_settings_media_device_label">Media device</string>
<!-- QuickSettings: RSSI [CHAR LIMIT=NONE] -->

View File

@@ -52,7 +52,7 @@
<com.android.systemui.tuner.StatusBarSwitch
android:key="cameratoggle"
android:title="Camera Toggle" />
android:title="@string/quick_settings_camera_label" />
<!-- nfc -->
<!-- tty -->

View File

@@ -40,6 +40,7 @@ import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.InternetTile;
import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.MicrophoneToggleTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.ReduceBrightColorsTile;
@@ -85,6 +86,7 @@ public class QSFactoryImpl implements QSFactory {
private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
private final Provider<ReduceBrightColorsTile> mReduceBrightColorsTileProvider;
private final Provider<CameraToggleTile> mCameraToggleTileProvider;
private final Provider<MicrophoneToggleTile> mMicrophoneToggleTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -118,7 +120,8 @@ public class QSFactoryImpl implements QSFactory {
Provider<UiModeNightTile> uiModeNightTileProvider,
Provider<ScreenRecordTile> screenRecordTileProvider,
Provider<ReduceBrightColorsTile> reduceBrightColorsTileProvider,
Provider<CameraToggleTile> cameraToggleTileProvider) {
Provider<CameraToggleTile> cameraToggleTileProvider,
Provider<MicrophoneToggleTile> microphoneToggleTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -147,6 +150,7 @@ public class QSFactoryImpl implements QSFactory {
mScreenRecordTileProvider = screenRecordTileProvider;
mReduceBrightColorsTileProvider = reduceBrightColorsTileProvider;
mCameraToggleTileProvider = cameraToggleTileProvider;
mMicrophoneToggleTileProvider = microphoneToggleTileProvider;
}
public QSTile createTile(String tileSpec) {
@@ -204,6 +208,8 @@ public class QSFactoryImpl implements QSFactory {
return mReduceBrightColorsTileProvider.get();
case "cameratoggle":
return mCameraToggleTileProvider.get();
case "mictoggle":
return mMicrophoneToggleTileProvider.get();
}
// Custom tiles

View File

@@ -16,33 +16,31 @@
package com.android.systemui.qs.tiles;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.content.Intent;
import android.annotation.StringRes;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.CameraToggleController;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import javax.inject.Inject;
public class CameraToggleTile extends QSTileImpl<QSTile.BooleanState> implements
CameraToggleController.Callback {
private CameraToggleController mCameraToggleController;
public class CameraToggleTile extends SensorPrivacyToggleTile {
@Inject
protected CameraToggleTile(QSHost host,
@@ -52,11 +50,9 @@ public class CameraToggleTile extends QSTileImpl<QSTile.BooleanState> implements
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
CameraToggleController cameraToggleController) {
IndividualSensorPrivacyController sensorPrivacyController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mCameraToggleController = cameraToggleController;
mCameraToggleController.observe(getLifecycle(), this);
activityStarter, qsLogger, sensorPrivacyController);
}
@Override
@@ -68,46 +64,17 @@ public class CameraToggleTile extends QSTileImpl<QSTile.BooleanState> implements
}
@Override
public BooleanState newTileState() {
return new BooleanState();
public @DrawableRes int getIconRes() {
return R.drawable.ic_camera_blocked;
}
@Override
protected void handleClick() {
mCameraToggleController.setCameraBlocked(!mCameraToggleController.isCameraBlocked());
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
boolean isBlocked = arg == null ? mCameraToggleController.setCameraBlocked()
: (boolean) arg;
state.icon = ResourceIcon.get(R.drawable.ic_camera_blocked);
state.state = isBlocked ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.value = isBlocked;
state.label = getTileLabel();
state.handlesLongClick = false;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
}
@Override
public int getMetricsCategory() {
return 0;
}
@Override
public Intent getLongClickIntent() {
return null;
}
@Override
public CharSequence getTileLabel() {
public @NonNull CharSequence getTileLabel() {
return mContext.getString(R.string.quick_settings_camera_label);
}
@Override
public void onCameraBlockedChanged(boolean enable) {
refreshState(enable);
public int getSensorId() {
return CAMERA;
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2020 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.qs.tiles;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfig;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import javax.inject.Inject;
public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
@Inject
protected MicrophoneToggleTile(QSHost host,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
IndividualSensorPrivacyController sensorPrivacyController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger, sensorPrivacyController);
}
@Override
public boolean isAvailable() {
return whitelistIpcs(() -> DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
"mic_toggle_enabled",
false));
}
@Override
public @DrawableRes int getIconRes() {
return R.drawable.ic_mic_blocked;
}
@Override
public @NonNull CharSequence getTileLabel() {
return mContext.getString(R.string.quick_settings_mic_label);
}
@Override
public int getSensorId() {
return MICROPHONE;
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 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.qs.tiles;
import android.content.Intent;
import android.hardware.SensorPrivacyManager.IndividualSensor;
import android.os.Handler;
import android.os.Looper;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import androidx.annotation.DrawableRes;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
/**
* Superclass to toggle individual sensor privacy via quick settings tiles
*/
public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanState> implements
IndividualSensorPrivacyController.Callback {
private IndividualSensorPrivacyController mSensorPrivacyController;
/**
* @return Id of the sensor that will be toggled
*/
public abstract @IndividualSensor int getSensorId();
/**
* @return icon for the QS tile
*/
public abstract @DrawableRes int getIconRes();
protected SensorPrivacyToggleTile(QSHost host,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
MetricsLogger metricsLogger,
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
IndividualSensorPrivacyController sensorPrivacyController) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
mSensorPrivacyController.observe(getLifecycle(), this);
}
@Override
public BooleanState newTileState() {
return new BooleanState();
}
@Override
protected void handleClick() {
mSensorPrivacyController.setSensorBlocked(getSensorId(),
!mSensorPrivacyController.isSensorBlocked(getSensorId()));
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
boolean isBlocked = arg == null ? mSensorPrivacyController.isSensorBlocked(getSensorId())
: (boolean) arg;
state.icon = ResourceIcon.get(getIconRes());
state.state = isBlocked ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.value = isBlocked;
state.label = getTileLabel();
state.handlesLongClick = false;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
}
@Override
public int getMetricsCategory() {
return 0;
}
@Override
public Intent getLongClickIntent() {
return null;
}
@Override
public void onSensorBlockedChanged(int sensor, boolean blocked) {
if (sensor == getSensorId()) {
refreshState(blocked);
}
}
}

View File

@@ -16,14 +16,16 @@
package com.android.systemui.statusbar.policy;
public interface CameraToggleController extends
CallbackController<CameraToggleController.Callback> {
import android.hardware.SensorPrivacyManager.IndividualSensor;
boolean isCameraBlocked();
public interface IndividualSensorPrivacyController extends
CallbackController<IndividualSensorPrivacyController.Callback> {
void setCameraBlocked(boolean blocked);
boolean isSensorBlocked(@IndividualSensor int sensor);
void setSensorBlocked(@IndividualSensor int sensor, boolean blocked);
interface Callback {
void onCameraBlockedChanged(boolean blocked);
void onSensorBlockedChanged(@IndividualSensor int sensor, boolean blocked);
}
}

View File

@@ -17,10 +17,13 @@
package com.android.systemui.statusbar.policy;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.CAMERA;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.MICROPHONE;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.IndividualSensor;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
import androidx.annotation.NonNull;
@@ -28,30 +31,36 @@ import java.util.Set;
import javax.inject.Inject;
public class CameraToggleControllerImpl implements CameraToggleController {
public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPrivacyController {
private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
private final @NonNull Context mContext;
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
private boolean mState;
private Set<Callback> mCallbacks = new ArraySet<>();
private final SparseBooleanArray mState = new SparseBooleanArray();
private final Set<Callback> mCallbacks = new ArraySet<>();
@Inject
public CameraToggleControllerImpl(@NonNull Context context) {
public IndividualSensorPrivacyControllerImpl(@NonNull Context context) {
mContext = context;
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
mSensorPrivacyManager.addSensorPrivacyListener(CAMERA, this::onCameraPrivacyChanged);
mState = mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(CAMERA);
for (int sensor : SENSORS) {
mSensorPrivacyManager.addSensorPrivacyListener(sensor,
(enabled) -> onSensorPrivacyChanged(sensor, enabled));
mState.put(sensor, mSensorPrivacyManager.isIndividualSensorPrivacyEnabled(sensor));
}
}
@Override
public boolean isCameraBlocked() {
return mState;
public boolean isSensorBlocked(@IndividualSensor int sensor) {
return mState.get(sensor, false);
}
@Override
public void setCameraBlocked(boolean blocked) {
mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(CAMERA, blocked);
public void setSensorBlocked(@IndividualSensor int sensor, boolean blocked) {
mSensorPrivacyManager.setIndividualSensorPrivacyForProfileGroup(sensor, blocked);
}
@Override
@@ -64,10 +73,11 @@ public class CameraToggleControllerImpl implements CameraToggleController {
mCallbacks.remove(listener);
}
private void onCameraPrivacyChanged(boolean state) {
mState = state;
private void onSensorPrivacyChanged(@IndividualSensor int sensor, boolean blocked) {
mState.put(sensor, blocked);
for (Callback callback : mCallbacks) {
callback.onCameraBlockedChanged(mState);
callback.onSensorBlockedChanged(sensor, blocked);
}
}
}

View File

@@ -24,8 +24,6 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.AccessPointControllerImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.CameraToggleController;
import com.android.systemui.statusbar.policy.CameraToggleControllerImpl;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.ExtensionController;
@@ -34,6 +32,8 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.KeyguardStateControllerImpl;
import com.android.systemui.statusbar.policy.LocationController;
@@ -122,7 +122,8 @@ public interface StatusBarPolicyModule {
/** */
@Binds
CameraToggleController provideCameraToggleController(CameraToggleControllerImpl controllerImpl);
IndividualSensorPrivacyController provideIndividualSensorPrivacyController(
IndividualSensorPrivacyControllerImpl controllerImpl);
/** */
@SysUISingleton