Add Sensors Off QS tile and display status bar icon when active

This CL will replace the SensorPrivacyTile with a new tile that can be
enabled from the developer settings. When this new tile is enabled only
the camera, mic, and sensors controlled by the SensorManager will be
disabled; the location and airplane mode tiles will not be modified.
The user will be notified when this tile is enabled with the sensors
off icon in the status bar.

Fixes: 126618519
Test: Manually verified airplane and location are not modified when sensors
      off is enabled; also verified icon is displayed when mode is active.

Change-Id: Iabf099d0d76c015015ce9859edc0b225ec554020
This commit is contained in:
Michael Groover
2019-03-19 18:56:36 -07:00
parent e4edae3798
commit a51ea81f44
16 changed files with 213 additions and 445 deletions

View File

@@ -8561,38 +8561,6 @@ public final class Settings {
public static final String PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE =
"packages_to_clear_data_before_full_restore";
/**
* Indicates the location state should be maintained after sensor privacy is disabled.
* @hide
*/
public static final String MAINTAIN_LOCATION_AFTER_SP_DISABLED = "0";
/**
* Indicates location should be reenabled after sensor privacy is disabled.
* @hide
*/
public static final String REENABLE_LOCATION_AFTER_SP_DISABLED = "1";
/**
* Indicates the state of airplane mode should be maintained after sensor privacy is
* disabled.
* @hide
*/
public static final String MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED = "0";
/**
* Indicates airplane mode should be disabled after sensor privacy is disabled.
* @hide
*/
public static final String DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED = "1";
/**
* The state of all sensors managed by SensorPrivacyService when sensor privacy is enabled.
* @hide
*/
public static final String SENSOR_PRIVACY_SENSOR_STATE =
"sensor_privacy_sensor_state";
/**
* Setting to determine whether to use the new notification priority handling features.
* @hide

View File

@@ -590,6 +590,13 @@ enum Action {
// OS: P
DIALOG_SWITCH_HFP_DEVICES = 1416;
// OPEN: QS Sensor Privacy Mode tile shown
// ACTION: QS Sensor Privacy Mode tile tapped
// SUBTYPE: 0 is off, 1 is on
// CATEGORY: QUICK_SETTINGS
// OS: Q
QS_SENSOR_PRIVACY = 1598;
// ACTION: Tap & Pay -> Default Application Setting -> Use Forground
ACTION_NFC_PAYMENT_FOREGROUND_SETTING = 1622;

View File

@@ -58,6 +58,7 @@
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_airplane</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_battery</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_sensors_off</xliff:g></item>
</string-array>
<string translatable="false" name="status_bar_rotate">rotate</string>
@@ -92,6 +93,7 @@
<string translatable="false" name="status_bar_microphone">microphone</string>
<string translatable="false" name="status_bar_camera">camera</string>
<string translatable="false" name="status_bar_airplane">airplane</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window

View File

@@ -2923,6 +2923,7 @@
<java-symbol type="string" name="status_bar_vpn" />
<java-symbol type="string" name="status_bar_microphone" />
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />

View File

@@ -623,7 +623,6 @@ public class SettingsBackupTest {
Settings.Secure.DEFAULT_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED,
Settings.Secure.DISABLED_PRINT_SERVICES,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -647,8 +646,6 @@ public class SettingsBackupTest {
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED,
Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED,
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
Settings.Secure.NFC_PAYMENT_FOREGROUND,
@@ -661,7 +658,6 @@ public class SettingsBackupTest {
Settings.Secure.PARENTAL_CONTROL_LAST_UPDATE,
Settings.Secure.PAYMENT_SERVICE_SEARCH_URI,
Settings.Secure.PRINT_SERVICE_SEARCH_URI,
Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED,
Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT, // Candidate?
Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY,
Settings.Secure.SEARCH_MAX_RESULTS_PER_SOURCE,
@@ -685,7 +681,6 @@ public class SettingsBackupTest {
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
Settings.Secure.SELECTED_SPELL_CHECKER, // Intentionally removed in Q
Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, // Intentionally removed in Q
Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE,
Settings.Secure.SETTINGS_CLASSNAME,
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, // candidate?
Settings.Secure.SHOW_ROTATION_SUGGESTIONS,

View File

@@ -1,28 +0,0 @@
<!--
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.
-->
<vector android:height="48dp" android:viewportHeight="5"
android:viewportWidth="5" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#00000000"
android:pathData="m4.762,0.661 l-4.233,4.233"
android:strokeAlpha="1" android:strokeColor="#000000"
android:strokeLineCap="round" android:strokeLineJoin="miter" android:strokeWidth=".5"/>
<path android:fillColor="#00000000"
android:pathData="M0.265,2.778L1.058,2.778l0.529,-1.323 0.529,2.646 0.529,-3.175 0.529,2.646 0.529,-1.587 0.265,0.794h1.058"
android:strokeAlpha="1" android:strokeColor="#000000"
android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth=".33"/>
</vector>

View File

@@ -0,0 +1,41 @@
<!--
Copyright (C) 2019 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:insetLeft="3dp"
android:insetRight="3dp"
android:width="17dp"
android:height="17dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M21.966,2 L2,22"
android:strokeLineCap="round"
android:strokeColor="#000000"
android:fillColor="#00000000"
android:strokeWidth="1.6521436"
android:strokeLineJoin="miter"
android:strokeAlpha="1"/>
<path
android:pathData="M0.752,12L4.496,12l2.496,-6.25 2.496,12.5 2.496,-15 2.496,12.5 2.496,-7.5 1.248,3.75h4.992"
android:strokeLineCap="round"
android:strokeColor="#000000"
android:fillColor="#00000000"
android:strokeWidth="1.25090861"
android:strokeLineJoin="round"
android:strokeAlpha="1"/>
</vector>

View File

@@ -97,6 +97,7 @@ import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -291,6 +292,7 @@ public class Dependency extends SystemUI {
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
@Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
@Inject
public Dependency() {
@@ -461,6 +463,7 @@ public class Dependency extends SystemUI {
mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
mProviders.put(SensorPrivacyController.class, mSensorPrivacyController::get);
// TODO(b/118592525): to support multi-display , we start to add something which is

View File

@@ -60,6 +60,8 @@ import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityController;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.SensorPrivacyControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -222,6 +224,12 @@ public abstract class DependencyBinder {
@Binds
public abstract PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl);
/**
*/
@Binds
public abstract SensorPrivacyController provideSensorPrivacyControllerImpl(
SensorPrivacyControllerImpl controllerImpl);
/**
*/
@Binds

View File

@@ -41,7 +41,6 @@ import com.android.systemui.qs.tiles.LocationTile;
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.qs.tiles.SensorPrivacyTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
import com.android.systemui.qs.tiles.WorkModeTile;
@@ -73,7 +72,6 @@ public class QSFactoryImpl implements QSFactory {
private final Provider<DataSaverTile> mDataSaverTileProvider;
private final Provider<NightDisplayTile> mNightDisplayTileProvider;
private final Provider<NfcTile> mNfcTileProvider;
private final Provider<SensorPrivacyTile> mSensorPrivacyTileProvider;
private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
private QSTileHost mHost;
@@ -96,7 +94,6 @@ public class QSFactoryImpl implements QSFactory {
Provider<DataSaverTile> dataSaverTileProvider,
Provider<NightDisplayTile> nightDisplayTileProvider,
Provider<NfcTile> nfcTileProvider,
Provider<SensorPrivacyTile> sensorPrivacyTileProvider,
Provider<GarbageMonitor.MemoryTile> memoryTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
@@ -115,7 +112,6 @@ public class QSFactoryImpl implements QSFactory {
mDataSaverTileProvider = dataSaverTileProvider;
mNightDisplayTileProvider = nightDisplayTileProvider;
mNfcTileProvider = nfcTileProvider;
mSensorPrivacyTileProvider = sensorPrivacyTileProvider;
mMemoryTileProvider = memoryTileProvider;
}
@@ -168,8 +164,6 @@ public class QSFactoryImpl implements QSFactory {
return mNightDisplayTileProvider.get();
case "nfc":
return mNfcTileProvider.get();
case "sensorprivacy":
return mSensorPrivacyTileProvider.get();
}
// Intent tiles.

View File

@@ -1,131 +0,0 @@
/*
* 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.systemui.qs.tiles;
import android.content.Intent;
import android.hardware.SensorPrivacyManager;
import android.service.quicksettings.Tile;
import android.widget.Switch;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import javax.inject.Inject;
/** Quick settings tile: SensorPrivacy mode **/
public class SensorPrivacyTile extends QSTileImpl<BooleanState> implements
SensorPrivacyManager.OnSensorPrivacyChangedListener {
private static final String TAG = "SensorPrivacy";
private final Icon mIcon =
ResourceIcon.get(R.drawable.ic_signal_sensors);
private final KeyguardMonitor mKeyguard;
private final SensorPrivacyManager mSensorPrivacyManager;
private final ActivityStarter mActivityStarter;
@Inject
public SensorPrivacyTile(QSHost host, SensorPrivacyManager sensorPrivacyManager,
KeyguardMonitor keyguardMonitor, ActivityStarter activityStarter) {
super(host);
mSensorPrivacyManager = sensorPrivacyManager;
mKeyguard = keyguardMonitor;
mActivityStarter = activityStarter;
}
@Override
public BooleanState newTileState() {
return new BooleanState();
}
@Override
public void handleClick() {
final boolean wasEnabled = mState.value;
// Don't allow disabling from the lockscreen.
if (wasEnabled && mKeyguard.isSecure() && mKeyguard.isShowing()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
setEnabled(!wasEnabled);
});
return;
}
MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
setEnabled(!wasEnabled);
}
private void setEnabled(boolean enabled) {
mSensorPrivacyManager.setSensorPrivacy(enabled);
}
@Override
public CharSequence getTileLabel() {
return mContext.getString(R.string.sensor_privacy_mode);
}
@Override
public Intent getLongClickIntent() {
return new Intent();
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean enabled = arg instanceof Boolean ? (Boolean) arg
: mSensorPrivacyManager.isSensorPrivacyEnabled();
state.value = enabled;
state.label = mContext.getString(R.string.sensor_privacy_mode);
state.icon = mIcon;
state.state = enabled ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
}
@Override
public int getMetricsCategory() {
return MetricsEvent.QS_SENSOR_PRIVACY;
}
@Override
protected String composeChangeAnnouncement() {
if (mState.value) {
return mContext
.getString(R.string.accessibility_quick_settings_sensor_privacy_changed_on);
} else {
return mContext
.getString(R.string.accessibility_quick_settings_sensor_privacy_changed_off);
}
}
@Override
protected void handleSetListening(boolean listening) {
if (listening) {
mSensorPrivacyManager.addSensorPrivacyListener(this);
} else {
mSensorPrivacyManager.removeSensorPrivacyListener(this);
}
}
@Override
public void onSensorPrivacyChanged(boolean enabled) {
refreshState(enabled);
}
}

View File

@@ -60,6 +60,7 @@ import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
import com.android.systemui.statusbar.policy.SensorPrivacyController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -100,6 +101,7 @@ public class PhoneStatusBarPolicy
private final String mSlotLocation;
private final String mSlotMicrophone;
private final String mSlotCamera;
private final String mSlotSensorsOff;
private final Context mContext;
private final Handler mHandler = new Handler();
@@ -118,6 +120,7 @@ public class PhoneStatusBarPolicy
private final LocationController mLocationController;
private final PrivacyItemController mPrivacyItemController;
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
private final SensorPrivacyController mSensorPrivacyController;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -149,6 +152,7 @@ public class PhoneStatusBarPolicy
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mLocationController = Dependency.get(LocationController.class);
mPrivacyItemController = Dependency.get(PrivacyItemController.class);
mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = context.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -165,6 +169,7 @@ public class PhoneStatusBarPolicy
mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
mSlotSensorsOff = context.getString(com.android.internal.R.string.status_bar_sensors_off);
// listen for broadcasts
IntentFilter filter = new IntentFilter();
@@ -232,6 +237,11 @@ public class PhoneStatusBarPolicy
mContext.getString(R.string.accessibility_location_active));
mIconController.setIconVisibility(mSlotLocation, false);
// sensors off
mIconController.setIcon(mSlotSensorsOff, R.drawable.stat_sys_sensors_off, null);
mIconController.setIconVisibility(mSlotSensorsOff,
mSensorPrivacyController.isSensorPrivacyEnabled());
mRotationLockController.addCallback(this);
mBluetooth.addCallback(this);
mProvisionedController.addCallback(this);
@@ -242,6 +252,7 @@ public class PhoneStatusBarPolicy
mDataSaver.addCallback(this);
mKeyguardMonitor.addCallback(this);
mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
}
@@ -514,6 +525,16 @@ public class PhoneStatusBarPolicy
}
};
private final SensorPrivacyController.OnSensorPrivacyChangedListener mSensorPrivacyListener =
new SensorPrivacyController.OnSensorPrivacyChangedListener() {
@Override
public void onSensorPrivacyChanged(boolean enabled) {
mHandler.post(() -> {
mIconController.setIconVisibility(mSlotSensorsOff, enabled);
});
}
};
@Override
public void appTransitionStarting(int displayId, long startTime, long duration,
boolean forced) {

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2019 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.statusbar.policy;
/**
* Interface for classes to control sensor privacy state and notification.
*/
public interface SensorPrivacyController extends
CallbackController<SensorPrivacyController.OnSensorPrivacyChangedListener> {
/**
* Returns whether sensor privacy is enabled.
*/
boolean isSensorPrivacyEnabled();
/**
* Interface for classes to receive callbacks when sensor privacy state changes.
*/
interface OnSensorPrivacyChangedListener {
void onSensorPrivacyChanged(boolean enabled);
}
}

View File

@@ -0,0 +1,94 @@
/*
* Copyright (C) 2019 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.statusbar.policy;
import android.content.Context;
import android.hardware.SensorPrivacyManager;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Controls sensor privacy state and notification.
*/
@Singleton
public class SensorPrivacyControllerImpl implements SensorPrivacyController,
SensorPrivacyManager.OnSensorPrivacyChangedListener {
private SensorPrivacyManager mSensorPrivacyManager;
private final List<OnSensorPrivacyChangedListener> mListeners;
private Object mLock = new Object();
private boolean mSensorPrivacyEnabled;
/**
* Public constructor.
*/
@Inject
public SensorPrivacyControllerImpl(Context context) {
mSensorPrivacyManager = (SensorPrivacyManager) context.getSystemService(
Context.SENSOR_PRIVACY_SERVICE);
mSensorPrivacyEnabled = mSensorPrivacyManager.isSensorPrivacyEnabled();
mSensorPrivacyManager.addSensorPrivacyListener(this);
mListeners = new ArrayList<>(1);
}
/**
* Returns whether sensor privacy is enabled.
*/
public boolean isSensorPrivacyEnabled() {
synchronized (mLock) {
return mSensorPrivacyEnabled;
}
}
/**
* Adds the provided listener for callbacks when sensor privacy state changes.
*/
public void addCallback(OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
mListeners.add(listener);
notifyListenerLocked(listener);
}
}
/**
* Removes the provided listener from callbacks when sensor privacy state changes.
*/
public void removeCallback(OnSensorPrivacyChangedListener listener) {
synchronized (mLock) {
mListeners.remove(listener);
}
}
/**
* Callback invoked by the SensorPrivacyService when sensor privacy state changes.
*/
public void onSensorPrivacyChanged(boolean enabled) {
synchronized (mLock) {
mSensorPrivacyEnabled = enabled;
for (OnSensorPrivacyChangedListener listener : mListeners) {
notifyListenerLocked(listener);
}
}
}
private void notifyListenerLocked(OnSensorPrivacyChangedListener listener) {
listener.onSensorPrivacyChanged(mSensorPrivacyEnabled);
}
}

View File

@@ -1,119 +0,0 @@
/*
* 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.systemui.qs.tiles;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.hardware.SensorPrivacyManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class SensorPrivacyTileTest extends SysuiTestCase {
@Mock
private KeyguardMonitor mKeyguard;
@Mock
private QSTileHost mHost;
@Mock
SensorPrivacyManager mSensorPrivacyManager;
private TestableLooper mTestableLooper;
private SensorPrivacyTile mSensorPrivacyTile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
mKeyguard = mDependency.injectMockDependency(KeyguardMonitor.class);
mSensorPrivacyManager = mDependency.injectMockDependency(SensorPrivacyManager.class);
when(mHost.getContext()).thenReturn(mContext);
mSensorPrivacyTile = new SensorPrivacyTile(mHost, mSensorPrivacyManager, mKeyguard,
mock(ActivityStarter.class));
}
@Test
public void testSensorPrivacyListenerAdded_handleListeningTrue() {
// To prevent access to privacy related features from apps with WRITE_SECURE_SETTINGS the
// sensor privacy state is not stored in Settings; to receive notification apps must add
// themselves as a listener with the SensorPrivacyManager. This test verifies when
// setListening is called with a value of true the tile adds itself as a listener.
mSensorPrivacyTile.handleSetListening(true);
mTestableLooper.processAllMessages();
verify(mSensorPrivacyManager).addSensorPrivacyListener(mSensorPrivacyTile);
}
@Test
public void testSensorPrivacyListenerRemoved_handleListeningFalse() {
// Similar to the test above verifies that the tile removes itself as a listener when
// setListening is called with a value of false.
mSensorPrivacyTile.handleSetListening(false);
mTestableLooper.processAllMessages();
verify(mSensorPrivacyManager).removeSensorPrivacyListener((mSensorPrivacyTile));
}
@Test
public void testSensorPrivacyEnabled_handleClick() {
// Verifies when the SensorPrivacy tile is clicked it invokes the SensorPrivacyManager to
// set sensor privacy.
mSensorPrivacyTile.getState().value = false;
mSensorPrivacyTile.handleClick();
mTestableLooper.processAllMessages();
verify(mSensorPrivacyManager).setSensorPrivacy(true);
mSensorPrivacyTile.getState().value = true;
mSensorPrivacyTile.handleClick();
mTestableLooper.processAllMessages();
verify(mSensorPrivacyManager).setSensorPrivacy(false);
}
@Test
public void testSensorPrivacyNotDisabled_keyguard() {
// Verifies when the device is locked that sensor privacy cannot be disabled
when(mKeyguard.isSecure()).thenReturn(true);
when(mKeyguard.isShowing()).thenReturn(true);
mSensorPrivacyTile.getState().value = true;
mSensorPrivacyTile.handleClick();
mTestableLooper.processAllMessages();
verify(mSensorPrivacyManager, never()).setSensorPrivacy(false);
}
}

View File

@@ -18,20 +18,15 @@ package com.android.server;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.app.ActivityManager;
import android.content.Context;
import android.hardware.ISensorPrivacyListener;
import android.hardware.ISensorPrivacyManager;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
@@ -277,8 +272,6 @@ public final class SensorPrivacyService extends SystemService {
}
}
mListeners.finishBroadcast();
// Handle the state of all sensors managed by this service.
SensorState.handleSensorPrivacyToggled(mContext, enabled);
}
}
@@ -306,121 +299,4 @@ public final class SensorPrivacyService extends SystemService {
}
}
}
/**
* Maintains the state of the sensors when sensor privacy is enabled to return them to their
* original state when sensor privacy is disabled.
*/
private static final class SensorState {
private static Object sLock = new Object();
@GuardedBy("sLock")
private static SensorState sPreviousState;
private boolean mAirplaneEnabled;
private boolean mLocationEnabled;
SensorState(boolean airplaneEnabled, boolean locationEnabled) {
mAirplaneEnabled = airplaneEnabled;
mLocationEnabled = locationEnabled;
}
public static void handleSensorPrivacyToggled(Context context, boolean enabled) {
synchronized (sLock) {
SensorState state;
if (enabled) {
// if sensor privacy is being enabled then obtain the current state of the
// sensors to be persisted and restored when sensor privacy is disabled.
state = getCurrentSensorState(context);
} else {
// else obtain the previous sensor state to be restored, first from the saved
// state if available, otherwise attempt to read it from Settings.
if (sPreviousState != null) {
state = sPreviousState;
} else {
state = getPersistedSensorState(context);
}
// if the previous state is not available then return without attempting to
// modify the sensor state.
if (state == null) {
return;
}
}
// The SensorState represents the state of the sensor before sensor privacy was
// enabled; if airplane mode was not enabled then the state of airplane mode should
// be the same as the state of sensor privacy.
if (!state.mAirplaneEnabled) {
setAirplaneMode(context, enabled);
}
// Similar to airplane mode the state of location should be the opposite of sensor
// privacy mode, if it was enabled when sensor privacy was enabled then it should be
// disabled. If location is disabled when sensor privacy is enabled then it will be
// left disabled when sensor privacy is disabled.
if (state.mLocationEnabled) {
setLocationEnabled(context, !enabled);
}
// if sensor privacy is being enabled then persist the current state.
if (enabled) {
sPreviousState = state;
persistState(context, sPreviousState);
}
}
}
public static SensorState getCurrentSensorState(Context context) {
LocationManager locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
boolean airplaneEnabled = Settings.Global.getInt(context.getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
boolean locationEnabled = locationManager.isLocationEnabled();
return new SensorState(airplaneEnabled, locationEnabled);
}
public static void persistState(Context context, SensorState state) {
StringBuilder stateValue = new StringBuilder();
stateValue.append(state.mAirplaneEnabled
? Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED
: Settings.Secure.DISABLE_AIRPLANE_MODE_AFTER_SP_DISABLED);
stateValue.append(",");
stateValue.append(
state.mLocationEnabled ? Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED
: Settings.Secure.MAINTAIN_LOCATION_AFTER_SP_DISABLED);
Settings.Secure.putString(context.getContentResolver(),
Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE, stateValue.toString());
}
public static SensorState getPersistedSensorState(Context context) {
String persistedState = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.SENSOR_PRIVACY_SENSOR_STATE);
if (persistedState == null) {
Log.e(TAG, "The persisted sensor state could not be obtained from Settings");
return null;
}
String[] sensorStates = persistedState.split(",");
if (sensorStates.length < 2) {
Log.e(TAG, "The persisted sensor state does not contain the expected values: "
+ persistedState);
return null;
}
boolean airplaneEnabled = sensorStates[0].equals(
Settings.Secure.MAINTAIN_AIRPLANE_MODE_AFTER_SP_DISABLED);
boolean locationEnabled = sensorStates[1].equals(
Settings.Secure.REENABLE_LOCATION_AFTER_SP_DISABLED);
return new SensorState(airplaneEnabled, locationEnabled);
}
private static void setAirplaneMode(Context context, boolean enable) {
ConnectivityManager connectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
connectivityManager.setAirplaneMode(enable);
}
private static void setLocationEnabled(Context context, boolean enable) {
LocationManager locationManager = (LocationManager) context.getSystemService(
Context.LOCATION_SERVICE);
locationManager.setLocationEnabledForUser(enable,
UserHandle.of(ActivityManager.getCurrentUser()));
}
}
}