diff options
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 |