Merge "Add Sensors Off QS tile and display status bar icon when active"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7c80a0b..7d66b90 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8587,38 +8587,6 @@
                 "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
          */
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index f037407..25caafb 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -590,6 +590,13 @@
     // 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;
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 71e071c..de02286 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -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
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 764c9c4..ba964bb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2924,6 +2924,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" />
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 6fb4744..85947bd 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -623,7 +623,6 @@
                  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 @@
                  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,
@@ -662,7 +659,6 @@
                  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,
@@ -686,7 +682,6 @@
                  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,
diff --git a/packages/SystemUI/res/drawable/ic_signal_sensors.xml b/packages/SystemUI/res/drawable/ic_signal_sensors.xml
deleted file mode 100644
index faaddf6..0000000
--- a/packages/SystemUI/res/drawable/ic_signal_sensors.xml
+++ /dev/null
@@ -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>
-
diff --git a/packages/SystemUI/res/drawable/stat_sys_sensors_off.xml b/packages/SystemUI/res/drawable/stat_sys_sensors_off.xml
new file mode 100644
index 0000000..ca8f993
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_sensors_off.xml
@@ -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>
+
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b7d1900..70f2cce 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -97,6 +97,7 @@
 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 @@
     @Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
     @Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
     @Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
+    @Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
 
     @Inject
     public Dependency() {
@@ -461,6 +463,7 @@
         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
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
index 3c6f081..53050bf 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyBinder.java
@@ -60,6 +60,8 @@
 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;
@@ -225,5 +227,11 @@
     /**
      */
     @Binds
+    public abstract SensorPrivacyController provideSensorPrivacyControllerImpl(
+            SensorPrivacyControllerImpl controllerImpl);
+
+    /**
+     */
+    @Binds
     public abstract QSHost provideQsHost(QSTileHost controllerImpl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index d26cee9..2956ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -41,7 +41,6 @@
 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 @@
     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 @@
             Provider<DataSaverTile> dataSaverTileProvider,
             Provider<NightDisplayTile> nightDisplayTileProvider,
             Provider<NfcTile> nfcTileProvider,
-            Provider<SensorPrivacyTile> sensorPrivacyTileProvider,
             Provider<GarbageMonitor.MemoryTile> memoryTileProvider) {
         mWifiTileProvider = wifiTileProvider;
         mBluetoothTileProvider = bluetoothTileProvider;
@@ -115,7 +112,6 @@
         mDataSaverTileProvider = dataSaverTileProvider;
         mNightDisplayTileProvider = nightDisplayTileProvider;
         mNfcTileProvider = nfcTileProvider;
-        mSensorPrivacyTileProvider = sensorPrivacyTileProvider;
         mMemoryTileProvider = memoryTileProvider;
     }
 
@@ -168,8 +164,6 @@
                 return mNightDisplayTileProvider.get();
             case "nfc":
                 return mNfcTileProvider.get();
-            case "sensorprivacy":
-                return mSensorPrivacyTileProvider.get();
         }
 
         // Intent tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
deleted file mode 100644
index 7ee37d5..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyTile.java
+++ /dev/null
@@ -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);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4a86484..b7a7873 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -60,6 +60,7 @@
 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 @@
     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 @@
     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 @@
         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 @@
         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 @@
                 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 @@
         mDataSaver.addCallback(this);
         mKeyguardMonitor.addCallback(this);
         mPrivacyItemController.addCallback(this);
+        mSensorPrivacyController.addCallback(mSensorPrivacyListener);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
     }
@@ -514,6 +525,16 @@
                 }
             };
 
+    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) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyController.java
new file mode 100644
index 0000000..6d5ce60
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyController.java
@@ -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);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
new file mode 100644
index 0000000..5db6693
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensorPrivacyControllerImpl.java
@@ -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);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
deleted file mode 100644
index 6386c4c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/SensorPrivacyTileTest.java
+++ /dev/null
@@ -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);
-    }
-}
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 1cbcbe5..edeb049 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -18,20 +18,15 @@
 
 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 @@
                 }
             }
             mListeners.finishBroadcast();
-            // Handle the state of all sensors managed by this service.
-            SensorState.handleSensorPrivacyToggled(mContext, enabled);
         }
     }
 
@@ -306,121 +299,4 @@
             }
         }
     }
-
-    /**
-     * 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()));
-        }
-    }
 }