Merge "Add DeviceConfig flags to Privacy Indicators" into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
9faaa2733a
@@ -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() { }
|
||||
}
|
||||
|
||||
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -249,6 +249,15 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
|
||||
requestLayout();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user