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";
// Flag related to Privacy Indicators
/**
* Whether the Permissions Hub is showing.
*/
public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
private SystemUiDeviceConfigFlags() { }
}

View 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 -> {}
}
}
}
}

View 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

View 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) {

View File

@@ -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
*/

View 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))
}
}