Add support for HW privacy toggles in sensor unblock dialog

- Adapt strings for when HW toggle is blocking sensor access
- Use hw-toggle specific resource names that can be overlayed by OEMs
  to provide device-specific guidance (strings, icons)
- Call #onSensorBlockedChanged when going from blocked by SW toggle to
  blocked by HW toggle (still blocked, but for a different reason) to
  update UI
- Change action button to open corresponding sensor privacy Settings
  page if blocked by HW toggle. Open mic settings if both mic & camera
  are blocked, since assuming a 3-way-switch (unblocked -> mic blocked
  -> mic & camera blocked) unblocking mic will also unblock the camera.

Bug: 220717475
Test: manually, on device with physical mic toggle
Change-Id: I64e37f29e73fb48d1d53a35a4ff72d5a77c6a6bc
This commit is contained in:
Robert Horvath
2022-03-10 18:06:34 +01:00
committed by Iavor-Valentin Iftime
parent fdf11ed6f1
commit db0a9431dc
9 changed files with 310 additions and 45 deletions

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11,5c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5.17l1.82,1.82c0.11,-0.31 0.18,-0.64 0.18,-0.99V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v1.17l2,2V5zM2.81,2.81L1.39,4.22l11.65,11.65c-0.33,0.08 -0.68,0.13 -1.04,0.13 -2.76,0 -5,-2.24 -5,-5H5c0,3.53 2.61,6.43 6,6.92V21h2v-3.08c0.57,-0.08 1.12,-0.24 1.64,-0.46l5.14,5.14 1.41,-1.41L2.81,2.81zM19,11h-2c0,0.91 -0.26,1.75 -0.69,2.48l1.46,1.46C18.54,13.82 19,12.47 19,11z"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M15,8v2.67l5.85,5.85C20.94,16.37 21,16.2 21,16V8c0,-0.82 -0.94,-1.29 -1.6,-0.8L17,9V8c0,-1.1 -0.9,-2 -2,-2h-4.67l2,2H15z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M2,3.32l2.71,2.71C3.74,6.17 3,7 3,8v8c0,1.1 0.9,2 2,2h11.68l3,3l1.41,-1.41L3.41,1.91L2,3.32zM5,8h1.68l8,8H5V8z"/>
</vector>

View File

@@ -42,4 +42,8 @@
<!-- Whether to use window background blur for the volume dialog. -->
<bool name="config_volumeDialogUseBackgroundBlur">true</bool>
<!-- Whether to tint the icon of the sensor hardware privacy toggle unblock dialog.
Set to false if using a custom icon. -->
<bool name="config_unblockHwSensorIconEnableTint">true</bool>
</resources>

View File

@@ -57,4 +57,7 @@
<dimen name="privacy_chip_dot_bg_height">18dp</dimen>
<dimen name="privacy_chip_dot_bg_radius">9dp</dimen>
<dimen name="unblock_hw_sensor_icon_width">@dimen/bottom_sheet_icon_size</dimen>
<dimen name="unblock_hw_sensor_icon_height">@dimen/bottom_sheet_icon_size</dimen>
</resources>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<!-- Drawable shown for the sensor unblock dialog when blocked by the hardware camera toggle -->
<drawable name="unblock_hw_sensor_camera">@drawable/ic_videocam_off</drawable>
<!-- Drawable shown for the sensor unblock dialog when blocked by the hardware microphone toggle -->
<drawable name="unblock_hw_sensor_microphone">@drawable/ic_mic_off</drawable>
<!-- Drawable shown for the sensor unblock dialog when blocked by the hardware privacy toggle -->
<drawable name="unblock_hw_sensor_all">@drawable/ic_videocam_off</drawable>
<!-- Second drawable shown for the sensor unblock dialog when blocked by the hardware privacy toggle -->
<drawable name="unblock_hw_sensor_all_second">@drawable/ic_mic_off</drawable>
</resources>

View File

@@ -720,16 +720,52 @@
<string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string>
<!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_camera_dialog_title">Unblock device camera?</string>
<!--- Title of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=150] -->
<!--- Title of dialog triggered if both microphone and camera are disabled but an app tried to access them. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_camera_dialog_title">Unblock device camera and microphone?</string>
<!--- Content of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_start_use_mic_dialog_content">This unblocks access for all apps and services allowed to use your microphone.</string>
<!--- Content of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_start_use_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera.</string>
<!--- Content of dialog triggered if the camera is disabled but an app tried to access it. [CHAR LIMIT=NONE] -->
<!--- Title of dialog triggered if both microphone and camera are disabled but an app tried to access them. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera or microphone.</string>
<!--- Title of dialog triggered if the microphone is blocked by a hardware privacy switch but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_blocked_dialog_title">Microphone is blocked</string>
<!--- Title of dialog triggered if the camera is blocked by a hardware privacy switch but an app tried to access it. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_camera_blocked_dialog_title">Camera is blocked</string>
<!--- Title of dialog triggered if both microphone and camera are blocked by a hardware privacy switch but an app tried to access them. [CHAR LIMIT=150] -->
<string name="sensor_privacy_start_use_mic_camera_blocked_dialog_title">The mic &amp; camera are blocked</string>
<!--- Content of dialog triggered if the microphone is disabled by HW toggle but an app tried to access it. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_start_use_mic_blocked_dialog_content">
To unblock, move the privacy switch on your device to the microphone on position to allow
microphone access. Refer to the device manual to locate the privacy switch on your device.
</string>
<!--- Content of dialog triggered if the camera is disabled by HW toggle but an app tried to access it. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_start_use_camera_blocked_dialog_content">
To unblock, move the privacy switch on your device to the camera on position to allow camera
access. Refer to the device manual to locate the privacy switch on your device.
</string>
<!--- Content of dialog triggered if the camera is disabled by HW toggle but an app tried to access it. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_start_use_mic_camera_blocked_dialog_content">
To unblock them, move the privacy switch on your device to the unblocked position to allow
access. Refer to the device manual to locate the privacy switch on your device.
</string>
<!--- Content of toast triggered if the microphone privacy is unblocked while the HW toggle privacy dialog was shown. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_unblocked_toast_content">
Microphone available
</string>
<!--- Content of toast triggered if the camera privacy is unblocked while the HW toggle privacy dialog was shown. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_camera_unblocked_toast_content">
Camera available
</string>
<!--- Content of toast triggered if both mic and camera privacy is unblocked while the HW toggle privacy dialog was shown. [CHAR LIMIT=NONE] -->
<string name="sensor_privacy_mic_camera_unblocked_toast_content">
Microphone and camera available
</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

@@ -20,6 +20,11 @@ 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.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.util.Log;
@@ -28,6 +33,7 @@ import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -36,18 +42,29 @@ import com.android.systemui.tv.TvBottomSheetActivity;
import javax.inject.Inject;
/**
* Bottom sheet that is shown when the camera/mic sensors are blocked by the global toggle and
* allows the user to re-enable them.
* Bottom sheet that is shown when the camera/mic sensors are blocked by the global software toggle
* or physical privacy switch.
*/
public class TvUnblockSensorActivity extends TvBottomSheetActivity {
private static final String TAG = TvUnblockSensorActivity.class.getSimpleName();
private static final String ACTION_MANAGE_CAMERA_PRIVACY =
"android.settings.MANAGE_CAMERA_PRIVACY";
private static final String ACTION_MANAGE_MICROPHONE_PRIVACY =
"android.settings.MANAGE_MICROPHONE_PRIVACY";
private static final int ALL_SENSORS = Integer.MAX_VALUE;
private int mSensor = -1;
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 TvUnblockSensorActivity(
@@ -76,52 +93,145 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
}
mSensorPrivacyCallback = (sensor, blocked) -> {
if (mSensor == ALL_SENSORS) {
if (!mSensorPrivacyController.isSensorBlocked(CAMERA)
&& !mSensorPrivacyController.isSensorBlocked(MICROPHONE)) {
finish();
}
if (mSensor == ALL_SENSORS && !mSensorPrivacyController.isSensorBlocked(CAMERA)
&& !mSensorPrivacyController.isSensorBlocked(MICROPHONE)) {
showToastAndFinish();
} else if (this.mSensor == sensor && !blocked) {
finish();
showToastAndFinish();
} else {
updateUI();
}
};
initUI();
}
private void initUI() {
TextView title = findViewById(R.id.bottom_sheet_title);
TextView content = findViewById(R.id.bottom_sheet_body);
ImageView icon = findViewById(R.id.bottom_sheet_icon);
// mic icon if both icons are shown
ImageView secondIcon = findViewById(R.id.bottom_sheet_second_icon);
Button unblockButton = findViewById(R.id.bottom_sheet_positive_button);
Button cancelButton = findViewById(R.id.bottom_sheet_negative_button);
switch (mSensor) {
private void showToastAndFinish() {
final int toastMsgResId;
switch(mSensor) {
case MICROPHONE:
title.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
content.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
icon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
secondIcon.setVisibility(View.GONE);
toastMsgResId = R.string.sensor_privacy_mic_unblocked_toast_content;
break;
case CAMERA:
title.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
content.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
icon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
secondIcon.setVisibility(View.GONE);
toastMsgResId = R.string.sensor_privacy_camera_unblocked_toast_content;
break;
case ALL_SENSORS:
default:
title.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
content.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
icon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
secondIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
break;
}
unblockButton.setText(
Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
finish();
}
private boolean isBlockedByHardwareToggle() {
if (mSensor == ALL_SENSORS) {
return mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA)
|| mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE);
} else {
return mSensorPrivacyController.isSensorBlockedByHardwareToggle(mSensor);
}
}
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() {
if (isBlockedByHardwareToggle()) {
updateUiForHardwareToggle();
} else {
updateUiForSoftwareToggle();
}
}
private void updateUiForHardwareToggle() {
final Resources resources = getResources();
boolean micBlocked = (mSensor == MICROPHONE || mSensor == ALL_SENSORS)
&& mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE);
boolean cameraBlocked = (mSensor == CAMERA || mSensor == ALL_SENSORS)
&& mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA);
setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
if (micBlocked && cameraBlocked) {
mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
mContent.setText(
R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_content);
mIcon.setImageResource(R.drawable.unblock_hw_sensor_all);
Drawable secondIconDrawable = resources.getDrawable(
R.drawable.unblock_hw_sensor_all_second, getTheme());
if (secondIconDrawable == null) {
mSecondIcon.setVisibility(View.GONE);
} else {
mSecondIcon.setImageDrawable(secondIconDrawable);
}
} else if (cameraBlocked) {
mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_content);
mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
mSecondIcon.setVisibility(View.GONE);
} else if (micBlocked) {
mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
mContent.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_content);
mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
mSecondIcon.setVisibility(View.GONE);
}
// 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 updateUiForSoftwareToggle() {
setIconTint(true);
setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
switch (mSensor) {
case MICROPHONE:
mTitle.setText(R.string.sensor_privacy_start_use_mic_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);
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);
mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
mSecondIcon.setImageResource(
com.android.internal.R.drawable.perm_group_microphone);
break;
}
mPositiveButton.setText(
com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button);
unblockButton.setOnClickListener(v -> {
mPositiveButton.setOnClickListener(v -> {
if (mSensor == ALL_SENSORS) {
mSensorPrivacyController.setSensorBlocked(OTHER, CAMERA, false);
mSensorPrivacyController.setSensorBlocked(OTHER, MICROPHONE, false);
@@ -129,14 +239,43 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
mSensorPrivacyController.setSensorBlocked(OTHER, mSensor, false);
}
});
}
cancelButton.setText(android.R.string.cancel);
cancelButton.setOnClickListener(v -> finish());
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();
}
@Override
public void onResume() {
super.onResume();
updateUI();
mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
}

View File

@@ -27,6 +27,12 @@ public interface IndividualSensorPrivacyController extends
boolean isSensorBlocked(@Sensor int sensor);
/**
* Returns {@code true} if the given sensor is blocked by a hardware toggle, {@code false}
* if the sensor is not blocked or blocked by a software toggle.
*/
boolean isSensorBlockedByHardwareToggle(@Sensor int sensor);
void setSensorBlocked(@Source int source, @Sensor int sensor, boolean blocked);
void suppressSensorPrivacyReminders(int sensor, boolean suppress);

View File

@@ -22,6 +22,7 @@ import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
import android.hardware.SensorPrivacyManager;
import android.hardware.SensorPrivacyManager.Sensors.Sensor;
import android.hardware.SensorPrivacyManager.Sources.Source;
import android.hardware.SensorPrivacyManager.ToggleType;
import android.util.ArraySet;
import android.util.SparseBooleanArray;
@@ -34,7 +35,8 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
private static final int[] SENSORS = new int[] {CAMERA, MICROPHONE};
private final @NonNull SensorPrivacyManager mSensorPrivacyManager;
private final SparseBooleanArray mState = new SparseBooleanArray();
private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray();
private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray();
private final Set<Callback> mCallbacks = new ArraySet<>();
public IndividualSensorPrivacyControllerImpl(
@@ -44,11 +46,27 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
@Override
public void init() {
for (int sensor : SENSORS) {
mSensorPrivacyManager.addSensorPrivacyListener(sensor,
(s, enabled) -> onSensorPrivacyChanged(sensor, enabled));
mSensorPrivacyManager.addSensorPrivacyListener(
new SensorPrivacyManager.OnSensorPrivacyChangedListener() {
@Override
public void onSensorPrivacyChanged(SensorPrivacyChangedParams params) {
IndividualSensorPrivacyControllerImpl.this.onSensorPrivacyChanged(
params.getSensor(), params.getToggleType(), params.isEnabled());
}
mState.put(sensor, mSensorPrivacyManager.isSensorPrivacyEnabled(sensor));
@Override
public void onSensorPrivacyChanged(int sensor, boolean enabled) {
// handled in onSensorPrivacyChanged(SensorPrivacyChangedParams)
}
});
for (int sensor : SENSORS) {
boolean softwarePrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, sensor);
boolean hardwarePrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled(
SensorPrivacyManager.TOGGLE_TYPE_HARDWARE, sensor);
mSoftwareToggleState.put(sensor, softwarePrivacyEnabled);
mHardwareToggleState.put(sensor, hardwarePrivacyEnabled);
}
}
@@ -59,7 +77,12 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
@Override
public boolean isSensorBlocked(@Sensor int sensor) {
return mState.get(sensor, false);
return mSoftwareToggleState.get(sensor, false) || mHardwareToggleState.get(sensor, false);
}
@Override
public boolean isSensorBlockedByHardwareToggle(@Sensor int sensor) {
return mHardwareToggleState.get(sensor, false);
}
@Override
@@ -82,11 +105,16 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr
mCallbacks.remove(listener);
}
private void onSensorPrivacyChanged(@Sensor int sensor, boolean blocked) {
mState.put(sensor, blocked);
private void onSensorPrivacyChanged(@Sensor int sensor, @ToggleType int toggleType,
boolean enabled) {
if (toggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
mSoftwareToggleState.put(sensor, enabled);
} else if (toggleType == SensorPrivacyManager.TOGGLE_TYPE_HARDWARE) {
mHardwareToggleState.put(sensor, enabled);
}
for (Callback callback : mCallbacks) {
callback.onSensorBlockedChanged(sensor, blocked);
callback.onSensorBlockedChanged(sensor, isSensorBlocked(sensor));
}
}
}