Merge "Add DeviceConfig flags to Privacy Indicators" into qt-dev

This commit is contained in:
TreeHugger Robot
2019-05-18 06:14:40 +00:00
committed by Android (Google) Code Review
6 changed files with 181 additions and 26 deletions

View File

@@ -106,5 +106,12 @@ public final class SystemUiDeviceConfigFlags {
*/ */
public static final String HASH_SALT_MAX_DAYS = "hash_salt_max_days"; 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() { } private SystemUiDeviceConfigFlags() { }
} }

View File

@@ -23,9 +23,13 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Handler import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager import android.os.UserManager
import android.provider.DeviceConfig
import com.android.internal.annotations.VisibleForTesting 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.BG_HANDLER_NAME
import com.android.systemui.Dependency.MAIN_HANDLER_NAME import com.android.systemui.Dependency.MAIN_HANDLER_NAME
import com.android.systemui.R import com.android.systemui.R
@@ -39,6 +43,9 @@ import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
fun isPermissionsHubEnabled() = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
@Singleton @Singleton
class PrivacyItemController @Inject constructor( class PrivacyItemController @Inject constructor(
val context: Context, val context: Context,
@@ -47,7 +54,8 @@ class PrivacyItemController @Inject constructor(
@Named(BG_HANDLER_NAME) private val bgHandler: Handler @Named(BG_HANDLER_NAME) private val bgHandler: Handler
) : Dumpable { ) : Dumpable {
companion object { @VisibleForTesting
internal companion object {
val OPS = intArrayOf(AppOpsManager.OP_CAMERA, val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_RECORD_AUDIO,
AppOpsManager.OP_COARSE_LOCATION, AppOpsManager.OP_COARSE_LOCATION,
@@ -57,6 +65,9 @@ class PrivacyItemController @Inject constructor(
Intent.ACTION_MANAGED_PROFILE_REMOVED) Intent.ACTION_MANAGED_PROFILE_REMOVED)
const val TAG = "PrivacyItemController" const val TAG = "PrivacyItemController"
const val SYSTEM_UID = 1000 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 @VisibleForTesting
@@ -70,6 +81,7 @@ class PrivacyItemController @Inject constructor(
val systemApp = val systemApp =
PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context) PrivacyApplication(context.getString(R.string.device_services), SYSTEM_UID, context)
private val callbacks = mutableListOf<WeakReference<Callback>>() private val callbacks = mutableListOf<WeakReference<Callback>>()
private val messageHandler = H(WeakReference(this), uiHandler.looper)
private val notifyChanges = Runnable { private val notifyChanges = Runnable {
val list = privacyList val list = privacyList
@@ -81,6 +93,20 @@ class PrivacyItemController @Inject constructor(
uiHandler.post(notifyChanges) 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 { private val cb = object : AppOpsController.Callback {
override fun onActiveStateChanged( override fun onActiveStateChanged(
code: Int, code: Int,
@@ -103,6 +129,11 @@ class PrivacyItemController @Inject constructor(
registerReceiver() registerReceiver()
} }
init {
DeviceConfig.addOnPropertyChangedListener(
DeviceConfig.NAMESPACE_PRIVACY, context.mainExecutor, devicePropertyChangedListener)
}
private fun unregisterReceiver() { private fun unregisterReceiver() {
context.unregisterReceiver(userSwitcherReceiver) context.unregisterReceiver(userSwitcherReceiver)
} }
@@ -123,8 +154,14 @@ class PrivacyItemController @Inject constructor(
bgHandler.post(updateListAndNotifyChanges) 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 if (listening == listen) return
listening = listen listening = listen
if (listening) { if (listening) {
@@ -134,32 +171,44 @@ class PrivacyItemController @Inject constructor(
} else { } else {
appOpsController.removeCallback(OPS, cb) appOpsController.removeCallback(OPS, cb)
unregisterReceiver() 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>) { private fun addCallback(callback: WeakReference<Callback>) {
callbacks.add(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 // 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>) { private fun removeCallback(callback: WeakReference<Callback>) {
// Removes also if the callback is null // Removes also if the callback is null
callbacks.removeIf { it.get()?.equals(callback.get()) ?: true } 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) { fun addCallback(callback: Callback) {
addCallback(WeakReference(callback)) messageHandler.obtainMessage(MSG_ADD_CALLBACK, callback).sendToTarget()
} }
fun removeCallback(callback: Callback) { fun removeCallback(callback: Callback) {
removeCallback(WeakReference(callback)) messageHandler.obtainMessage(MSG_REMOVE_CALLBACK, callback).sendToTarget()
} }
private fun updatePrivacyList() { private fun updatePrivacyList() {
if (!listening) {
privacyList = emptyList()
return
}
val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) } val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct() .mapNotNull { toPrivacyItem(it) }.distinct()
privacyList = list 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 -> {}
}
}
}
} }

View File

@@ -34,6 +34,7 @@ import android.media.AudioManager;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.provider.AlarmClock; import android.provider.AlarmClock;
import android.provider.DeviceConfig;
import android.provider.Settings; import android.provider.Settings;
import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils; import android.text.format.DateUtils;
@@ -54,6 +55,7 @@ import android.widget.TextView;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView; import com.android.systemui.BatteryMeterView;
import com.android.systemui.DualToneHandler; 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.PrivacyDialogBuilder;
import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyItemControllerKt;
import com.android.systemui.qs.QSDetail.Callback; import com.android.systemui.qs.QSDetail.Callback;
import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -141,6 +144,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private OngoingPrivacyChip mPrivacyChip; private OngoingPrivacyChip mPrivacyChip;
private Space mSpace; private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon; private BatteryMeterView mBatteryRemainingIcon;
private boolean mPermissionsHubEnabled;
private PrivacyItemController mPrivacyItemController; private PrivacyItemController mPrivacyItemController;
@@ -154,6 +158,20 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private boolean mHasTopCutout = false; private boolean mHasTopCutout = false;
private boolean mPrivacyChipLogged = 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() { private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
@Override @Override
public void privacyChanged(List<PrivacyItem> privacyItems) { public void privacyChanged(List<PrivacyItem> privacyItems) {
@@ -236,6 +254,12 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE); mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
mRingerModeTextView.setSelected(true); mRingerModeTextView.setSelected(true);
mNextAlarmTextView.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() { private List<String> getIgnoredIconSlots() {
@@ -244,8 +268,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements
com.android.internal.R.string.status_bar_camera)); com.android.internal.R.string.status_bar_camera));
ignored.add(mContext.getResources().getString( ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_microphone)); com.android.internal.R.string.status_bar_microphone));
if (mPermissionsHubEnabled) {
ignored.add(mContext.getResources().getString( ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_location)); com.android.internal.R.string.status_bar_location));
}
return ignored; return ignored;
} }
@@ -262,7 +288,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
} }
private void setChipVisibility(boolean chipVisible) { private void setChipVisibility(boolean chipVisible) {
if (chipVisible) { if (chipVisible && mPermissionsHubEnabled) {
mPrivacyChip.setVisibility(View.VISIBLE); mPrivacyChip.setVisibility(View.VISIBLE);
// Makes sure that the chip is logged as viewed at most once each time QS is opened // 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 // mListening makes sure that the callback didn't return after the user closed QS

View File

@@ -44,6 +44,7 @@ import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.UiOffloadThread; import com.android.systemui.UiOffloadThread;
import com.android.systemui.privacy.PrivacyItem; import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController; import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyItemControllerKt;
import com.android.systemui.privacy.PrivacyType; import com.android.systemui.privacy.PrivacyType;
import com.android.systemui.qs.tiles.DndTile; import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.RotationLockTile;
@@ -82,7 +83,8 @@ public class PhoneStatusBarPolicy
ZenModeController.Callback, ZenModeController.Callback,
DeviceProvisionedListener, DeviceProvisionedListener,
KeyguardMonitor.Callback, KeyguardMonitor.Callback,
PrivacyItemController.Callback { PrivacyItemController.Callback,
LocationController.LocationChangeCallback {
private static final String TAG = "PhoneStatusBarPolicy"; private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -257,6 +259,7 @@ public class PhoneStatusBarPolicy
mKeyguardMonitor.addCallback(this); mKeyguardMonitor.addCallback(this);
mPrivacyItemController.addCallback(this); mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener); mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this); SysUiServiceProvider.getComponent(mContext, CommandQueue.class).addCallback(this);
} }
@@ -635,6 +638,20 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotLocation, showLocation); 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() { private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {

View File

@@ -249,6 +249,15 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
requestLayout(); 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 * Layout is happening from end -> start
*/ */

View File

@@ -24,13 +24,14 @@ import android.content.pm.UserInfo
import android.os.Handler import android.os.Handler
import android.os.UserHandle import android.os.UserHandle
import android.os.UserManager 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.AndroidTestingRunner
import android.testing.TestableLooper import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper 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
import com.android.systemui.Dependency.BG_HANDLER
import com.android.systemui.Dependency.MAIN_HANDLER
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpItem 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.hasItem
import org.hamcrest.Matchers.not import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue import org.hamcrest.Matchers.nullValue
import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertThat import org.junit.Assert.assertThat
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@@ -106,6 +108,9 @@ class PrivacyItemControllerTest : SysuiTestCase() {
mContext.addMockSystemService(UserManager::class.java, userManager) mContext.addMockSystemService(UserManager::class.java, userManager)
mContext.getOrCreateTestableResources().addOverride(R.string.device_services, mContext.getOrCreateTestableResources().addOverride(R.string.device_services,
DEVICE_SERVICES_STRING) DEVICE_SERVICES_STRING)
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
"true", false)
doReturn(listOf(object : UserInfo() { doReturn(listOf(object : UserInfo() {
init { init {
@@ -116,9 +121,15 @@ class PrivacyItemControllerTest : SysuiTestCase() {
privacyItemController = PrivacyItemController(mContext) privacyItemController = PrivacyItemController(mContext)
} }
@After
fun tearDown() {
DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, DeviceConfig.NAMESPACE_PRIVACY)
}
@Test @Test
fun testSetListeningTrueByAddingCallback() { fun testSetListeningTrueByAddingCallback() {
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
testableLooper.processAllMessages()
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
any(AppOpsController.Callback::class.java)) any(AppOpsController.Callback::class.java))
testableLooper.processAllMessages() testableLooper.processAllMessages()
@@ -126,18 +137,16 @@ class PrivacyItemControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testSetListeningTrue() { fun testSetListeningFalseByRemovingLastCallback() {
privacyItemController.setListening(true) privacyItemController.addCallback(callback)
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), testableLooper.processAllMessages()
verify(appOpsController, never()).removeCallback(any(IntArray::class.java),
any(AppOpsController.Callback::class.java)) any(AppOpsController.Callback::class.java))
} privacyItemController.removeCallback(callback)
testableLooper.processAllMessages()
@Test
fun testSetListeningFalse() {
privacyItemController.setListening(true)
privacyItemController.setListening(false)
verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS), verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
any(AppOpsController.Callback::class.java)) any(AppOpsController.Callback::class.java))
verify(callback).privacyChanged(emptyList())
} }
@Test @Test
@@ -168,7 +177,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
fun testRegisterReceiver_allUsers() { fun testRegisterReceiver_allUsers() {
val spiedContext = spy(mContext) val spiedContext = spy(mContext)
val itemController = PrivacyItemController(spiedContext) val itemController = PrivacyItemController(spiedContext)
itemController.setListening(true) itemController.addCallback(callback)
testableLooper.processAllMessages()
verify(spiedContext, atLeastOnce()).registerReceiverAsUser( verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null), eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
eq(null)) eq(null))
@@ -268,4 +278,16 @@ class PrivacyItemControllerTest : SysuiTestCase() {
assertEquals(list, privacyList) assertEquals(list, privacyList)
assertTrue(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))
}
} }