OSDN Git Service

Add DeviceConfig flags to Privacy Indicators
authorFabian Kozynski <kozynski@google.com>
Mon, 6 May 2019 14:18:41 +0000 (10:18 -0400)
committerFabian Kozynski <kozynski@google.com>
Fri, 17 May 2019 16:19:05 +0000 (12:19 -0400)
Restricts Privacy Indicators behind the same flags as Permissions Hub.

When flag is set to false:
* PrivacyItemController is set to not listening (listens to changes in
flag)
* Location icon is reverted back to P behavior
* Location icon is shown in QS Header icons (listens to changes in
flags)
* No privacy chip shown.

We need to listen to changes because most of our views/controllers
persist forever.

Test: manual, change the flag and observe things appear/disappear
Fixes: 131826452
Fixes: 132644949

Change-Id: I329596760f3c06feb2161b077af008da782a50be

core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt

index 6d0a864..14fe6ab 100644 (file)
@@ -106,5 +106,12 @@ public final class SystemUiDeviceConfigFlags {
      */
     public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days";
 
+    // Flag related to Privacy Indicators
+
+    /**
+     * Whether the Permissions Hub is showing.
+     */
+    public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
+
     private SystemUiDeviceConfigFlags() { }
 }
index 1c0974a..82a2c1f 100644 (file)
@@ -23,9 +23,13 @@ import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.Handler
+import android.os.Looper
+import android.os.Message
 import android.os.UserHandle
 import android.os.UserManager
+import android.provider.DeviceConfig
 import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.systemui.Dependency.BG_HANDLER_NAME
 import com.android.systemui.Dependency.MAIN_HANDLER_NAME
 import com.android.systemui.R
@@ -39,6 +43,9 @@ import javax.inject.Inject
 import javax.inject.Named
 import javax.inject.Singleton
 
+fun isPermissionsHubEnabled() = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
+
 @Singleton
 class PrivacyItemController @Inject constructor(
         val context: Context,
@@ -47,7 +54,8 @@ class PrivacyItemController @Inject constructor(
         @Named(BG_HANDLER_NAME) private val bgHandler: Handler
 ) : Dumpable {
 
-    companion object {
+    @VisibleForTesting
+    internal companion object {
         val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
                 AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_COARSE_LOCATION,
@@ -57,6 +65,9 @@ class PrivacyItemController @Inject constructor(
                 Intent.ACTION_MANAGED_PROFILE_REMOVED)
         const val TAG = "PrivacyItemController"
         const val SYSTEM_UID = 1000
+        const val MSG_ADD_CALLBACK = 0
+        const val MSG_REMOVE_CALLBACK = 1
+        const val MSG_UPDATE_LISTENING_STATE = 2
     }
 
     @VisibleForTesting
@@ -70,6 +81,7 @@ class PrivacyItemController @Inject constructor(
     val systemApp =
             PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
     private val callbacks = mutableListOf<WeakReference<Callback>>()
+    private val messageHandler = H(WeakReference(this), uiHandler.looper)
 
     private val notifyChanges = Runnable {
         val list = privacyList
@@ -81,6 +93,20 @@ class PrivacyItemController @Inject constructor(
         uiHandler.post(notifyChanges)
     }
 
+    private var indicatorsAvailable = isPermissionsHubEnabled()
+    @VisibleForTesting
+    internal val devicePropertyChangedListener =
+            object : DeviceConfig.OnPropertyChangedListener {
+        override fun onPropertyChanged(namespace: String, name: String, value: String?) {
+            if (DeviceConfig.NAMESPACE_PRIVACY.equals(namespace) &&
+                    SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED.equals(name)) {
+                indicatorsAvailable = java.lang.Boolean.parseBoolean(value)
+                messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
+                messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
+            }
+        }
+    }
+
     private val cb = object : AppOpsController.Callback {
         override fun onActiveStateChanged(
             code: Int,
@@ -103,6 +129,11 @@ class PrivacyItemController @Inject constructor(
             registerReceiver()
         }
 
+    init {
+        DeviceConfig.addOnPropertyChangedListener(
+                DeviceConfig.NAMESPACE_PRIVACY, context.mainExecutor, devicePropertyChangedListener)
+    }
+
     private fun unregisterReceiver() {
         context.unregisterReceiver(userSwitcherReceiver)
     }
@@ -123,8 +154,14 @@ class PrivacyItemController @Inject constructor(
         bgHandler.post(updateListAndNotifyChanges)
     }
 
-    @VisibleForTesting
-    internal fun setListening(listen: Boolean) {
+    /**
+     * Updates listening status based on whether there are callbacks and the indicators are enabled
+     *
+     * This is only called from private (add/remove)Callback and from the config listener, all in
+     * main thread.
+     */
+    private fun setListeningState() {
+        val listen = !callbacks.isEmpty() and indicatorsAvailable
         if (listening == listen) return
         listening = listen
         if (listening) {
@@ -134,32 +171,44 @@ class PrivacyItemController @Inject constructor(
         } else {
             appOpsController.removeCallback(OPS, cb)
             unregisterReceiver()
+            // Make sure that we remove all indicators and notify listeners if we are not
+            // listening anymore due to indicators being disabled
+            update(false)
         }
     }
 
     private fun addCallback(callback: WeakReference<Callback>) {
         callbacks.add(callback)
-        if (callbacks.isNotEmpty() && !listening) setListening(true)
+        if (callbacks.isNotEmpty() && !listening) {
+            messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
+            messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
+        }
         // Notify this callback if we didn't set to listening
-        else uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
+        else if (listening) uiHandler.post(NotifyChangesToCallback(callback.get(), privacyList))
     }
 
     private fun removeCallback(callback: WeakReference<Callback>) {
         // Removes also if the callback is null
         callbacks.removeIf { it.get()?.equals(callback.get()) ?: true }
-        if (callbacks.isEmpty()) setListening(false)
+        if (callbacks.isEmpty()) {
+            messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
+            messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
+        }
     }
 
     fun addCallback(callback: Callback) {
-        addCallback(WeakReference(callback))
+        messageHandler.obtainMessage(MSG_ADD_CALLBACK, callback).sendToTarget()
     }
 
     fun removeCallback(callback: Callback) {
-        removeCallback(WeakReference(callback))
+        messageHandler.obtainMessage(MSG_REMOVE_CALLBACK, callback).sendToTarget()
     }
 
     private fun updatePrivacyList() {
-
+        if (!listening) {
+            privacyList = emptyList()
+            return
+        }
         val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
                 .mapNotNull { toPrivacyItem(it) }.distinct()
         privacyList = list
@@ -217,4 +266,29 @@ class PrivacyItemController @Inject constructor(
             }
         }
     }
+
+    private class H(
+            private val outerClass: WeakReference<PrivacyItemController>,
+            looper: Looper
+    ) : Handler(looper) {
+        override fun handleMessage(msg: Message) {
+            super.handleMessage(msg)
+            when (msg.what) {
+                MSG_UPDATE_LISTENING_STATE -> outerClass.get()?.setListeningState()
+
+                MSG_ADD_CALLBACK -> {
+                    if (msg.obj !is PrivacyItemController.Callback) return
+                    outerClass.get()?.addCallback(
+                            WeakReference(msg.obj as PrivacyItemController.Callback))
+                }
+
+                MSG_REMOVE_CALLBACK -> {
+                    if (msg.obj !is PrivacyItemController.Callback) return
+                    outerClass.get()?.removeCallback(
+                            WeakReference(msg.obj as PrivacyItemController.Callback))
+                }
+                else -> {}
+            }
+        }
+    }
 }
\ No newline at end of file
index 42c616c..ce3c04e 100644 (file)
@@ -34,6 +34,7 @@ import android.media.AudioManager;
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.AlarmClock;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.service.notification.ZenModeConfig;
 import android.text.format.DateUtils;
@@ -54,6 +55,7 @@ import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.settingslib.Utils;
 import com.android.systemui.BatteryMeterView;
 import com.android.systemui.DualToneHandler;
@@ -65,6 +67,7 @@ import com.android.systemui.privacy.OngoingPrivacyChip;
 import com.android.systemui.privacy.PrivacyDialogBuilder;
 import com.android.systemui.privacy.PrivacyItem;
 import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyItemControllerKt;
 import com.android.systemui.qs.QSDetail.Callback;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -141,6 +144,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
     private OngoingPrivacyChip mPrivacyChip;
     private Space mSpace;
     private BatteryMeterView mBatteryRemainingIcon;
+    private boolean mPermissionsHubEnabled;
 
     private PrivacyItemController mPrivacyItemController;
 
@@ -154,6 +158,20 @@ public class QuickStatusBarHeader extends RelativeLayout implements
     private boolean mHasTopCutout = false;
     private boolean mPrivacyChipLogged = false;
 
+    private final DeviceConfig.OnPropertyChangedListener mPropertyListener =
+            new DeviceConfig.OnPropertyChangedListener() {
+                @Override
+                public void onPropertyChanged(String namespace, String name, String value) {
+                    if (DeviceConfig.NAMESPACE_PRIVACY.equals(namespace)
+                            && SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED.equals(
+                            name)) {
+                        mPermissionsHubEnabled = Boolean.valueOf(value);
+                        StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
+                        iconContainer.setIgnoredSlots(getIgnoredIconSlots());
+                    }
+                }
+            };
+
     private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
         @Override
         public void privacyChanged(List<PrivacyItem> privacyItems) {
@@ -236,6 +254,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements
         mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
         mRingerModeTextView.setSelected(true);
         mNextAlarmTextView.setSelected(true);
+
+        mPermissionsHubEnabled = PrivacyItemControllerKt.isPermissionsHubEnabled();
+        // Change the ignored slots when DeviceConfig flag changes
+        DeviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+                mContext.getMainExecutor(), mPropertyListener);
+
     }
 
     private List<String> getIgnoredIconSlots() {
@@ -244,8 +268,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements
                 com.android.internal.R.string.status_bar_camera));
         ignored.add(mContext.getResources().getString(
                 com.android.internal.R.string.status_bar_microphone));
-        ignored.add(mContext.getResources().getString(
-                com.android.internal.R.string.status_bar_location));
+        if (mPermissionsHubEnabled) {
+            ignored.add(mContext.getResources().getString(
+                    com.android.internal.R.string.status_bar_location));
+        }
 
         return ignored;
     }
@@ -262,7 +288,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
     }
 
     private void setChipVisibility(boolean chipVisible) {
-        if (chipVisible) {
+        if (chipVisible && mPermissionsHubEnabled) {
             mPrivacyChip.setVisibility(View.VISIBLE);
             // Makes sure that the chip is logged as viewed at most once each time QS is opened
             // mListening makes sure that the callback didn't return after the user closed QS
index 50e406f..ee43879 100644 (file)
@@ -44,6 +44,7 @@ import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.privacy.PrivacyItem;
 import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.PrivacyItemControllerKt;
 import com.android.systemui.privacy.PrivacyType;
 import com.android.systemui.qs.tiles.DndTile;
 import com.android.systemui.qs.tiles.RotationLockTile;
@@ -82,7 +83,8 @@ public class PhoneStatusBarPolicy
                 ZenModeController.Callback,
                 DeviceProvisionedListener,
                 KeyguardMonitor.Callback,
-                PrivacyItemController.Callback {
+                PrivacyItemController.Callback,
+                LocationController.LocationChangeCallback {
     private static final String TAG = "PhoneStatusBarPolicy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -257,6 +259,7 @@ public class PhoneStatusBarPolicy
         mKeyguardMonitor.addCallback(this);
         mPrivacyItemController.addCallback(this);
         mSensorPrivacyController.addCallback(mSensorPrivacyListener);
+        mLocationController.addCallback(this);
 
         SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
     }
@@ -635,6 +638,20 @@ public class PhoneStatusBarPolicy
         mIconController.setIconVisibility(mSlotLocation, showLocation);
     }
 
+    @Override
+    public void onLocationActiveChanged(boolean active) {
+        if (!PrivacyItemControllerKt.isPermissionsHubEnabled()) updateLocation();
+    }
+
+    // Updates the status view based on the current state of location requests.
+    private void updateLocation() {
+        if (mLocationController.isLocationActive()) {
+            mIconController.setIconVisibility(mSlotLocation, true);
+        } else {
+            mIconController.setIconVisibility(mSlotLocation, false);
+        }
+    }
+
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
index 6e36c01..22006db 100644 (file)
@@ -250,6 +250,15 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
     }
 
     /**
+     * Sets the list of ignored icon slots clearing the current list.
+     * @param slots names of the icons to ignore
+     */
+    public void setIgnoredSlots(List<String> slots) {
+        mIgnoredSlots.clear();
+        addIgnoredSlots(slots);
+    }
+
+    /**
      * Layout is happening from end -> start
      */
     private void calculateIconTranslations() {
index 6033ed2..e2e0bb1 100644 (file)
@@ -24,13 +24,14 @@ import android.content.pm.UserInfo
 import android.os.Handler
 import android.os.UserHandle
 import android.os.UserManager
-import androidx.test.filters.SmallTest
+import android.provider.DeviceConfig
+import android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
 import com.android.systemui.Dependency
-import com.android.systemui.Dependency.BG_HANDLER
-import com.android.systemui.Dependency.MAIN_HANDLER
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.appops.AppOpItem
@@ -38,6 +39,7 @@ import com.android.systemui.appops.AppOpsController
 import org.hamcrest.Matchers.hasItem
 import org.hamcrest.Matchers.not
 import org.hamcrest.Matchers.nullValue
+import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertThat
 import org.junit.Assert.assertTrue
@@ -106,6 +108,9 @@ class PrivacyItemControllerTest : SysuiTestCase() {
         mContext.addMockSystemService(UserManager::class.java, userManager)
         mContext.getOrCreateTestableResources().addOverride(R.string.device_services,
                 DEVICE_SERVICES_STRING)
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
+                "true", false)
 
         doReturn(listOf(object : UserInfo() {
             init {
@@ -116,9 +121,15 @@ class PrivacyItemControllerTest : SysuiTestCase() {
         privacyItemController = PrivacyItemController(mContext)
     }
 
+    @After
+    fun tearDown() {
+        DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_PRIVACY)
+    }
+
     @Test
     fun testSetListeningTrueByAddingCallback() {
         privacyItemController.addCallback(callback)
+        testableLooper.processAllMessages()
         verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
                 any(AppOpsController.Callback::class.java))
         testableLooper.processAllMessages()
@@ -126,18 +137,16 @@ class PrivacyItemControllerTest : SysuiTestCase() {
     }
 
     @Test
-    fun testSetListeningTrue() {
-        privacyItemController.setListening(true)
-        verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
+    fun testSetListeningFalseByRemovingLastCallback() {
+        privacyItemController.addCallback(callback)
+        testableLooper.processAllMessages()
+        verify(appOpsController, never()).removeCallback(any(IntArray::class.java),
                 any(AppOpsController.Callback::class.java))
-    }
-
-    @Test
-    fun testSetListeningFalse() {
-        privacyItemController.setListening(true)
-        privacyItemController.setListening(false)
+        privacyItemController.removeCallback(callback)
+        testableLooper.processAllMessages()
         verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
                 any(AppOpsController.Callback::class.java))
+        verify(callback).privacyChanged(emptyList())
     }
 
     @Test
@@ -168,7 +177,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
     fun testRegisterReceiver_allUsers() {
         val spiedContext = spy(mContext)
         val itemController = PrivacyItemController(spiedContext)
-        itemController.setListening(true)
+        itemController.addCallback(callback)
+        testableLooper.processAllMessages()
         verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
                 eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
                 eq(null))
@@ -268,4 +278,16 @@ class PrivacyItemControllerTest : SysuiTestCase() {
         assertEquals(list, privacyList)
         assertTrue(list !== privacyList)
     }
+
+    @Test
+    fun testNotListeningWhenIndicatorsDisabled() {
+        privacyItemController.devicePropertyChangedListener.onPropertyChanged(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
+                "false")
+        privacyItemController.addCallback(callback)
+        testableLooper.processAllMessages()
+        verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS),
+                any(AppOpsController.Callback::class.java))
+    }
 }
\ No newline at end of file