summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt48
6 files changed, 181 insertions, 26 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 6d0a8646b3a7..14fe6ab7df1c 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -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() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index 1c0974a0c987..82a2c1fb43fb 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -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
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 42c616c054f8..ce3c04e6c6be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -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
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 50e406f5936e..ee4387952792 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -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) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 6e36c019bb28..22006db47f19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -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() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index 6033ed26579c..e2e0bb151228 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -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