*/
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";
-
// Flags related to Assistant Handles
/**
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android">
- <solid android:color="#242424" /> <!-- 14% of white -->
- <padding android:paddingTop="@dimen/ongoing_appops_chip_bg_padding"
- android:paddingBottom="@dimen/ongoing_appops_chip_bg_padding" />
- <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" />
-</shape>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-
-<com.android.systemui.privacy.OngoingPrivacyChip
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/privacy_chip"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical|end"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:focusable="true" >
-
- <FrameLayout
- android:id="@+id/background"
- android:layout_height="@dimen/ongoing_appops_chip_height"
- android:minWidth="48dp"
- android:layout_width="wrap_content" >
- <LinearLayout
- android:id="@+id/icons_container"
- android:layout_height="match_parent"
- android:layout_width="wrap_content"
- android:gravity="center_vertical"
- />
- </FrameLayout>
-</com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2018 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<TextView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary"
-/>
\ No newline at end of file
android:layout_height="@*android:dimen/quick_qs_offset_height"
android:clipChildren="false"
android:clipToPadding="false"
- android:gravity="center"
android:orientation="horizontal"
android:clickable="true"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end" >
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center_vertical|start" >
-
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
systemui:showDark="false" />
- </LinearLayout>
-
- <android.widget.Space
- android:id="@+id/space"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_gravity="center_vertical|center_horizontal"
- android:visibility="gone" />
-
- <LinearLayout
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center_vertical|end" >
-
- <include layout="@layout/ongoing_privacy_chip" />
-
- </LinearLayout>
</LinearLayout>
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.PowerUI;
-import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
- @Inject Lazy<PrivacyItemController> mPrivacyItemController;
@Inject @Named(BG_LOOPER_NAME) Lazy<Looper> mBgLooper;
@Inject @Named(BG_HANDLER_NAME) Lazy<Handler> mBgHandler;
@Inject @Named(MAIN_HANDLER_NAME) Lazy<Handler> mMainHandler;
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
mProviders.put(ClockManager.class, mClockManager::get);
- mProviders.put(PrivacyItemController.class, mPrivacyItemController::get);
mProviders.put(ActivityManagerWrapper.class, mActivityManagerWrapper::get);
mProviders.put(DevicePolicyManagerWrapper.class, mDevicePolicyManagerWrapper::get);
mProviders.put(PackageManagerWrapper.class, mPackageManagerWrapper::get);
import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
}
/**
- * Does the app-op code refer to a user sensitive permission for the specified user id
- * and package. Only user sensitive permission should be shown to the user by default.
- *
- * @param appOpCode The code of the app-op.
- * @param uid The uid of the user.
- * @param packageName The name of the package.
- *
- * @return {@code true} iff the app-op item is user sensitive
- */
- private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
- String permission = AppOpsManager.opToPermission(appOpCode);
- if (permission == null) {
- return false;
- }
- int permFlags = mContext.getPackageManager().getPermissionFlags(permission,
- packageName, UserHandle.getUserHandleForUid(uid));
- return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- }
-
- /**
- * Does the app-op item refer to an operation that should be shown to the user.
- * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
- * permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op item should be shown to the user
- */
- private boolean isUserVisible(AppOpItem item) {
- return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
- }
-
-
- /**
- * Does the app-op, uid and package name, refer to an operation that should be shown to the
- * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
- * ops that refer to user sensitive permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op for should be shown to the user
- */
- private boolean isUserVisible(int appOpCode, int uid, String packageName) {
- // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
- // which may be user senstive, so for now always show it to the user.
- if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
- return true;
- }
-
- return isUserSensitive(appOpCode, uid, packageName);
- }
-
- /**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
* @return List of active AppOps information
final int numActiveItems = mActiveItems.size();
for (int i = 0; i < numActiveItems; i++) {
AppOpItem item = mActiveItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
final int numNotedItems = mNotedItems.size();
for (int i = 0; i < numNotedItems; i++) {
AppOpItem item = mNotedItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
}
private void notifySuscribers(int code, int uid, String packageName, boolean active) {
- if (mCallbacksByCode.containsKey(code)
- && isUserVisible(code, uid, packageName)) {
+ if (mCallbacksByCode.containsKey(code)) {
for (Callback cb: mCallbacksByCode.get(code)) {
cb.onActiveStateChanged(code, uid, packageName, active);
}
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.Gravity
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import android.widget.ImageView
-import android.widget.LinearLayout
-import com.android.systemui.R
-
-class OngoingPrivacyChip @JvmOverloads constructor(
- context: Context,
- attrs: AttributeSet? = null,
- defStyleAttrs: Int = 0,
- defStyleRes: Int = 0
-) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) {
-
- private val iconMarginExpanded = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_chip_icon_margin_expanded)
- private val iconMarginCollapsed = context.resources.getDimensionPixelSize(
- R.dimen.ongoing_appops_chip_icon_margin_collapsed)
- private val iconSize =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
- private val iconColor = context.resources.getColor(
- R.color.status_bar_clock_color, context.theme)
- private val sidePadding =
- context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
- private val backgroundDrawable = context.getDrawable(R.drawable.privacy_chip_bg)
- private lateinit var iconsContainer: LinearLayout
- private lateinit var back: FrameLayout
- var expanded = false
- set(value) {
- if (value != field) {
- field = value
- updateView()
- }
- }
-
- var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>())
- var privacyList = emptyList<PrivacyItem>()
- set(value) {
- field = value
- builder = PrivacyDialogBuilder(context, value)
- updateView()
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
-
- back = findViewById(R.id.background)
- iconsContainer = findViewById(R.id.icons_container)
- }
-
- // Should only be called if the builder icons or app changed
- private fun updateView() {
- back.background = if (expanded) backgroundDrawable else null
- val padding = if (expanded) sidePadding else 0
- back.setPaddingRelative(padding, 0, padding, 0)
- fun setIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: ViewGroup) {
- iconsContainer.removeAllViews()
- dialogBuilder.generateIcons().forEachIndexed { i, it ->
- it.mutate()
- it.setTint(iconColor)
- val image = ImageView(context).apply {
- setImageDrawable(it)
- scaleType = ImageView.ScaleType.CENTER_INSIDE
- }
- iconsContainer.addView(image, iconSize, iconSize)
- if (i != 0) {
- val lp = image.layoutParams as MarginLayoutParams
- lp.marginStart = if (expanded) iconMarginExpanded else iconMarginCollapsed
- image.layoutParams = lp
- }
- }
- }
-
- if (!privacyList.isEmpty()) {
- generateContentDescription()
- setIcons(builder, iconsContainer)
- val lp = iconsContainer.layoutParams as FrameLayout.LayoutParams
- lp.gravity = Gravity.CENTER_VERTICAL or
- (if (expanded) Gravity.CENTER_HORIZONTAL else Gravity.END)
- iconsContainer.layoutParams = lp
- } else {
- iconsContainer.removeAllViews()
- }
- requestLayout()
- }
-
- private fun generateContentDescription() {
- val typesText = builder.joinTypes()
- contentDescription = context.getString(
- R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import android.content.Context
-import android.graphics.drawable.Drawable
-import com.android.systemui.R
-
-class PrivacyDialogBuilder(private val context: Context, itemsList: List<PrivacyItem>) {
-
- val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>>
- val types: List<PrivacyType>
- private val separator = context.getString(R.string.ongoing_privacy_dialog_separator)
- private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator)
-
- init {
- appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType })
- .toList()
- .sortedWith(compareBy({ -it.second.size }, // Sort by number of AppOps
- { it.second.min() })) // Sort by "smallest" AppOpp (Location is largest)
- types = itemsList.map { it.privacyType }.distinct().sorted()
- }
-
- fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> {
- return types.sorted().map { it.getIcon(context) }
- }
-
- fun generateIcons() = types.map { it.getIcon(context) }
-
- private fun <T> List<T>.joinWithAnd(): StringBuilder {
- return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply {
- append(lastSeparator)
- append(this@joinWithAnd.last())
- }
- }
-
- fun joinTypes(): String {
- return when (types.size) {
- 0 -> ""
- 1 -> types[0].getName(context)
- else -> types.map { it.getName(context) }.joinWithAnd().toString()
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageManager
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import android.util.IconDrawableFactory
-import com.android.systemui.R
-
-typealias Privacy = PrivacyType
-
-enum class PrivacyType(private val nameId: Int, val iconId: Int) {
- // This is uses the icons used by the corresponding permission groups in the AndroidManifest
- TYPE_CAMERA(R.string.privacy_type_camera,
- com.android.internal.R.drawable.perm_group_camera),
- TYPE_MICROPHONE(R.string.privacy_type_microphone,
- com.android.internal.R.drawable.perm_group_microphone),
- TYPE_LOCATION(R.string.privacy_type_location,
- com.android.internal.R.drawable.perm_group_location);
-
- fun getName(context: Context) = context.resources.getString(nameId)
-
- fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
-}
-
-data class PrivacyItem(
- val privacyType: PrivacyType,
- val application: PrivacyApplication
-)
-
-data class PrivacyApplication(val packageName: String, val uid: Int, val context: Context)
- : Comparable<PrivacyApplication> {
-
- override fun compareTo(other: PrivacyApplication): Int {
- return applicationName.compareTo(other.applicationName)
- }
-
- private val applicationInfo: ApplicationInfo? by lazy {
- try {
- val userHandle = UserHandle.getUserHandleForUid(uid)
- context.createPackageContextAsUser(packageName, 0, userHandle).getPackageManager()
- .getApplicationInfo(packageName, 0)
- } catch (_: PackageManager.NameNotFoundException) {
- null
- }
- }
- val icon: Drawable by lazy {
- applicationInfo?.let {
- try {
- val iconFactory = IconDrawableFactory.newInstance(context, true)
- iconFactory.getBadgedIcon(it, UserHandle.getUserId(uid))
- } catch (_: Exception) {
- null
- }
- } ?: context.getDrawable(android.R.drawable.sym_def_app_icon)
- }
-
- val applicationName: String by lazy {
- applicationInfo?.let {
- context.packageManager.getApplicationLabel(it) as String
- } ?: packageName
- }
-
- override fun toString() = "PrivacyApplication(packageName=$packageName, uid=$uid)"
-}
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import android.app.ActivityManager
-import android.app.AppOpsManager
-import android.content.BroadcastReceiver
-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
-import com.android.systemui.appops.AppOpItem
-import com.android.systemui.appops.AppOpsController
-import com.android.systemui.Dumpable
-import java.io.FileDescriptor
-import java.io.PrintWriter
-import java.lang.ref.WeakReference
-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,
- private val appOpsController: AppOpsController,
- @Named(MAIN_HANDLER_NAME) private val uiHandler: Handler,
- @Named(BG_HANDLER_NAME) private val bgHandler: Handler
-) : Dumpable {
-
- @VisibleForTesting
- internal companion object {
- val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
- AppOpsManager.OP_RECORD_AUDIO,
- AppOpsManager.OP_COARSE_LOCATION,
- AppOpsManager.OP_FINE_LOCATION)
- val intents = listOf(Intent.ACTION_USER_FOREGROUND,
- Intent.ACTION_MANAGED_PROFILE_ADDED,
- 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
- internal var privacyList = emptyList<PrivacyItem>()
- @Synchronized get() = field.toList() // Returns a shallow copy of the list
- @Synchronized set
-
- private val userManager = context.getSystemService(UserManager::class.java)
- private var currentUserIds = emptyList<Int>()
- private var listening = false
- 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
- callbacks.forEach { it.get()?.privacyChanged(list) }
- }
-
- private val updateListAndNotifyChanges = Runnable {
- updatePrivacyList()
- 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,
- uid: Int,
- packageName: String,
- active: Boolean
- ) {
- val userId = UserHandle.getUserId(uid)
- if (userId in currentUserIds) {
- update(false)
- }
- }
- }
-
- @VisibleForTesting
- internal var userSwitcherReceiver = Receiver()
- set(value) {
- context.unregisterReceiver(field)
- field = value
- registerReceiver()
- }
-
- init {
- DeviceConfig.addOnPropertyChangedListener(
- DeviceConfig.NAMESPACE_PRIVACY, context.mainExecutor, devicePropertyChangedListener)
- }
-
- private fun unregisterReceiver() {
- context.unregisterReceiver(userSwitcherReceiver)
- }
-
- private fun registerReceiver() {
- context.registerReceiverAsUser(userSwitcherReceiver, UserHandle.ALL, IntentFilter().apply {
- intents.forEach {
- addAction(it)
- }
- }, null, null)
- }
-
- private fun update(updateUsers: Boolean) {
- if (updateUsers) {
- val currentUser = ActivityManager.getCurrentUser()
- currentUserIds = userManager.getProfiles(currentUser).map { it.id }
- }
- bgHandler.post(updateListAndNotifyChanges)
- }
-
- /**
- * 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) {
- appOpsController.addCallback(OPS, cb)
- registerReceiver()
- update(true)
- } 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) {
- messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
- messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
- }
- // Notify this callback if we didn't set to listening
- 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()) {
- messageHandler.removeMessages(MSG_UPDATE_LISTENING_STATE)
- messageHandler.sendEmptyMessage(MSG_UPDATE_LISTENING_STATE)
- }
- }
-
- fun addCallback(callback: Callback) {
- messageHandler.obtainMessage(MSG_ADD_CALLBACK, callback).sendToTarget()
- }
-
- fun removeCallback(callback: 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
- }
-
- private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
- val type: PrivacyType = when (appOpItem.code) {
- AppOpsManager.OP_CAMERA -> PrivacyType.TYPE_CAMERA
- AppOpsManager.OP_COARSE_LOCATION -> PrivacyType.TYPE_LOCATION
- AppOpsManager.OP_FINE_LOCATION -> PrivacyType.TYPE_LOCATION
- AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
- else -> return null
- }
- if (appOpItem.uid == SYSTEM_UID) return PrivacyItem(type, systemApp)
- val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid, context)
- return PrivacyItem(type, app)
- }
-
- // Used by containing class to get notified of changes
- interface Callback {
- fun privacyChanged(privacyItems: List<PrivacyItem>)
- }
-
- internal inner class Receiver : BroadcastReceiver() {
- override fun onReceive(context: Context?, intent: Intent?) {
- if (intent?.action in intents) {
- update(true)
- }
- }
- }
-
- private class NotifyChangesToCallback(
- private val callback: Callback?,
- private val list: List<PrivacyItem>
- ) : Runnable {
- override fun run() {
- callback?.privacyChanged(list)
- }
- }
-
- override fun dump(fd: FileDescriptor?, pw: PrintWriter?, args: Array<out String>?) {
- pw?.println("PrivacyItemController state:")
- pw?.println(" Listening: $listening")
- pw?.println(" Current user ids: $currentUserIds")
- pw?.println(" Privacy Items:")
- privacyList.forEach {
- pw?.print(" ")
- pw?.println(it.toString())
- }
- pw?.println(" Callbacks:")
- callbacks.forEach {
- it.get()?.let {
- pw?.print(" ")
- pw?.println(it.toString())
- }
- }
- }
-
- 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
import android.graphics.Rect;
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;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
-import android.util.StatsLog;
import android.view.ContextThemeWrapper;
import android.view.DisplayCutout;
import android.view.View;
import android.view.WindowInsets;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.RelativeLayout;
-import android.widget.Space;
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;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
-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;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Locale;
import java.util.Objects;
private TintedIconManager mIconManager;
private TouchAnimator mStatusIconsAlphaAnimator;
private TouchAnimator mHeaderTextContainerAlphaAnimator;
- private TouchAnimator mPrivacyChipAlphaAnimator;
private DualToneHandler mDualToneHandler;
private View mSystemIconsView;
private View mRingerContainer;
private Clock mClockView;
private DateView mDateView;
- private OngoingPrivacyChip mPrivacyChip;
- private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
- private boolean mPermissionsHubEnabled;
-
- private PrivacyItemController mPrivacyItemController;
private final BroadcastReceiver mRingerReceiver = new BroadcastReceiver() {
@Override
}
};
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) {
- mPrivacyChip.setPrivacyList(privacyItems);
- setChipVisibility(!privacyItems.isEmpty());
- }
- };
@Inject
public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
NextAlarmController nextAlarmController, ZenModeController zenModeController,
StatusBarIconController statusBarIconController,
- ActivityStarter activityStarter, PrivacyItemController privacyItemController) {
+ ActivityStarter activityStarter) {
super(context, attrs);
mAlarmController = nextAlarmController;
mZenController = zenModeController;
mStatusBarIconController = statusBarIconController;
mActivityStarter = activityStarter;
- mPrivacyItemController = privacyItemController;
mDualToneHandler = new DualToneHandler(
new ContextThemeWrapper(context, R.style.QSHeaderTheme));
}
mSystemIconsView = findViewById(R.id.quick_status_bar_system_icons);
mQuickQsStatusIcons = findViewById(R.id.quick_qs_status_icons);
StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
- // Ignore privacy icons because they show in the space above QQS
- iconContainer.addIgnoredSlots(getIgnoredIconSlots());
iconContainer.setShouldRestrictIcons(false);
mIconManager = new TintedIconManager(iconContainer);
mRingerModeIcon = findViewById(R.id.ringer_mode_icon);
mRingerModeTextView = findViewById(R.id.ringer_mode_text);
mRingerContainer = findViewById(R.id.ringer_container);
- mRingerContainer.setOnClickListener(this::onClick);
- mPrivacyChip = findViewById(R.id.privacy_chip);
- mPrivacyChip.setOnClickListener(this::onClick);
mCarrierGroup = findViewById(R.id.carrier_group);
mClockView = findViewById(R.id.clock);
mClockView.setOnClickListener(this);
mDateView = findViewById(R.id.date);
- mSpace = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
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() {
- ArrayList<String> ignored = new ArrayList<>();
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_camera));
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_microphone));
- if (mPermissionsHubEnabled) {
- ignored.add(mContext.getResources().getString(
- com.android.internal.R.string.status_bar_location));
- }
-
- return ignored;
}
private void updateStatusText() {
}
}
- private void setChipVisibility(boolean 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
- if (!mPrivacyChipLogged && mListening) {
- mPrivacyChipLogged = true;
- StatsLog.write(StatsLog.PRIVACY_INDICATORS_INTERACTED,
- StatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__CHIP_VIEWED);
- }
- } else {
- mPrivacyChip.setVisibility(View.GONE);
- }
- }
-
private boolean updateRingerStatus() {
boolean isOriginalVisible = mRingerModeTextView.getVisibility() == View.VISIBLE;
CharSequence originalRingerText = mRingerModeTextView.getText();
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
- updatePrivacyChipAlphaAnimator();
}
private void updateStatusIconAlphaAnimator() {
.build();
}
- private void updatePrivacyChipAlphaAnimator() {
- mPrivacyChipAlphaAnimator = new TouchAnimator.Builder()
- .addFloat(mPrivacyChip, "alpha", 1, 0, 1)
- .build();
- }
-
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mExpanded = expanded;
if (mHeaderTextContainerAlphaAnimator != null) {
mHeaderTextContainerAlphaAnimator.setPosition(keyguardExpansionFraction);
}
- if (mPrivacyChipAlphaAnimator != null) {
- mPrivacyChip.setExpanded(expansionFraction > 0.5);
- mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
- }
}
public void disable(int state1, int state2, boolean animate) {
mSystemIconsView.setPadding(padding.first, 0, padding.second, 0);
}
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mSpace.getLayoutParams();
- if (cutout != null) {
- Rect topCutout = cutout.getBoundingRectTop();
- if (topCutout.isEmpty()) {
- mHasTopCutout = false;
- lp.width = 0;
- mSpace.setVisibility(View.GONE);
- } else {
- mHasTopCutout = true;
- lp.width = topCutout.width();
- mSpace.setVisibility(View.VISIBLE);
- }
- }
- mSpace.setLayoutParams(lp);
- setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
return super.onApplyWindowInsets(insets);
}
mAlarmController.addCallback(this);
mContext.registerReceiver(mRingerReceiver,
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION));
- mPrivacyItemController.addCallback(mPICCallback);
} else {
mZenController.removeCallback(this);
mAlarmController.removeCallback(this);
- mPrivacyItemController.removeCallback(mPICCallback);
mContext.unregisterReceiver(mRingerReceiver);
- mPrivacyChipLogged = false;
}
}
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS), 0);
}
- } else if (v == mPrivacyChip) {
- // Makes sure that the builder is grabbed as soon as the chip is pressed
- PrivacyDialogBuilder builder = mPrivacyChip.getBuilder();
- if (builder.getAppsAndTypes().size() == 0) return;
- Handler mUiHandler = new Handler(Looper.getMainLooper());
- StatsLog.write(StatsLog.PRIVACY_INDICATORS_INTERACTED,
- StatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__CHIP_CLICKED);
- mUiHandler.post(() -> {
- mActivityStarter.postStartActivityDismissingKeyguard(
- new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
- mHost.collapsePanels();
- });
} else if (v == mRingerContainer && mRingerContainer.isVisibleToUser()) {
mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
Settings.ACTION_SOUND_SETTINGS), 0);
import com.android.systemui.R;
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;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
import java.util.Locale;
/**
ZenModeController.Callback,
DeviceProvisionedListener,
KeyguardMonitor.Callback,
- PrivacyItemController.Callback,
LocationController.LocationChangeCallback {
private static final String TAG = "PhoneStatusBarPolicy";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- public static final int LOCATION_STATUS_ICON_ID = PrivacyType.TYPE_LOCATION.getIconId();
+ public static final int LOCATION_STATUS_ICON_ID =
+ com.android.internal.R.drawable.perm_group_location;
private final String mSlotCast;
private final String mSlotHotspot;
private final String mSlotHeadset;
private final String mSlotDataSaver;
private final String mSlotLocation;
- private final String mSlotMicrophone;
- private final String mSlotCamera;
private final String mSlotSensorsOff;
private final Context mContext;
private final DeviceProvisionedController mProvisionedController;
private final KeyguardMonitor mKeyguardMonitor;
private final LocationController mLocationController;
- private final PrivacyItemController mPrivacyItemController;
private final UiOffloadThread mUiOffloadThread = Dependency.get(UiOffloadThread.class);
private final SensorPrivacyController mSensorPrivacyController;
mProvisionedController = Dependency.get(DeviceProvisionedController.class);
mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
mLocationController = Dependency.get(LocationController.class);
- mPrivacyItemController = Dependency.get(PrivacyItemController.class);
mSensorPrivacyController = Dependency.get(SensorPrivacyController.class);
mSlotCast = context.getString(com.android.internal.R.string.status_bar_cast);
mSlotHeadset = context.getString(com.android.internal.R.string.status_bar_headset);
mSlotDataSaver = context.getString(com.android.internal.R.string.status_bar_data_saver);
mSlotLocation = context.getString(com.android.internal.R.string.status_bar_location);
- mSlotMicrophone = context.getString(com.android.internal.R.string.status_bar_microphone);
- mSlotCamera = context.getString(com.android.internal.R.string.status_bar_camera);
mSlotSensorsOff = context.getString(com.android.internal.R.string.status_bar_sensors_off);
// listen for broadcasts
context.getString(R.string.accessibility_data_saver_on));
mIconController.setIconVisibility(mSlotDataSaver, false);
- // privacy items
- mIconController.setIcon(mSlotMicrophone, PrivacyType.TYPE_MICROPHONE.getIconId(),
- PrivacyType.TYPE_MICROPHONE.getName(mContext));
- mIconController.setIconVisibility(mSlotMicrophone, false);
- mIconController.setIcon(mSlotCamera, PrivacyType.TYPE_CAMERA.getIconId(),
- PrivacyType.TYPE_CAMERA.getName(mContext));
- mIconController.setIconVisibility(mSlotCamera, false);
mIconController.setIcon(mSlotLocation, LOCATION_STATUS_ICON_ID,
mContext.getString(R.string.accessibility_location_active));
mIconController.setIconVisibility(mSlotLocation, false);
mNextAlarmController.addCallback(mNextAlarmCallback);
mDataSaver.addCallback(this);
mKeyguardMonitor.addCallback(this);
- mPrivacyItemController.addCallback(this);
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
mLocationController.addCallback(this);
mIconController.setIconVisibility(mSlotDataSaver, isDataSaving);
}
- @Override // PrivacyItemController.Callback
- public void privacyChanged(List<PrivacyItem> privacyItems) {
- updatePrivacyItems(privacyItems);
- }
-
- private void updatePrivacyItems(List<PrivacyItem> items) {
- boolean showCamera = false;
- boolean showMicrophone = false;
- boolean showLocation = false;
- for (PrivacyItem item : items) {
- if (item == null /* b/124234367 */) {
- if (DEBUG) {
- Log.e(TAG, "updatePrivacyItems - null item found");
- StringWriter out = new StringWriter();
- mPrivacyItemController.dump(null, new PrintWriter(out), null);
- Log.e(TAG, out.toString());
- }
- continue;
- }
- switch (item.getPrivacyType()) {
- case TYPE_CAMERA:
- showCamera = true;
- break;
- case TYPE_LOCATION:
- showLocation = true;
- break;
- case TYPE_MICROPHONE:
- showMicrophone = true;
- break;
- }
- }
-
- mIconController.setIconVisibility(mSlotCamera, showCamera);
- mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
- mIconController.setIconVisibility(mSlotLocation, showLocation);
- }
-
@Override
public void onLocationActiveChanged(boolean active) {
- if (!PrivacyItemControllerKt.isPermissionsHubEnabled()) updateLocation();
+ updateLocation();
}
// Updates the status view based on the current state of location requests.
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = UserHandle.getUid(0, 0);
private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
- private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@Mock
private AppOpsManager mAppOpsManager;
getContext().addMockSystemService(AppOpsManager.class, mAppOpsManager);
- // All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
- // TEST_UID_NON_USER_SENSITIVE are user sensitive.
- getContext().setMockPackageManager(mPackageManager);
- when(mPackageManager.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mPackageManager.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mPackageManager.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
-
mController = new AppOpsControllerImpl(mContext, Dependency.get(Dependency.BG_LOOPER));
}
}
@Test
- public void nonUserSensitiveOpsAreIgnored() {
- mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
- TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
- assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
- }
-
- @Test
public void opNotedScheduledForRemoval() {
mController.setBGHandler(mMockHandler);
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
-import com.android.systemui.SysuiTestCase
-import org.junit.Assert.assertEquals
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-@SmallTest
-class PrivacyDialogBuilderTest : SysuiTestCase() {
-
- companion object {
- val TEST_UID = 1
- }
-
- @Test
- fun testGenerateAppsList() {
- val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Bar", TEST_UID, context))
- val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication(
- "Bar", TEST_UID, context))
- val foo0 = PrivacyItem(Privacy.TYPE_MICROPHONE, PrivacyApplication(
- "Foo", TEST_UID, context))
- val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication(
- "Baz", TEST_UID, context))
-
- val items = listOf(bar2, foo0, baz1, bar3)
-
- val textBuilder = PrivacyDialogBuilder(context, items)
-
- val list = textBuilder.appsAndTypes
- assertEquals(3, list.size)
- val appsList = list.map { it.first }
- val typesList = list.map { it.second }
- // List is sorted by number of types and then by types
- assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName })
- assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0])
- assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1])
- assertEquals(listOf(Privacy.TYPE_MICROPHONE), typesList[2])
- }
-
- @Test
- fun testOrder() {
- // We want location to always go last, so it will go in the "+ other apps"
- val appCamera = PrivacyItem(PrivacyType.TYPE_CAMERA,
- PrivacyApplication("Camera", TEST_UID, context))
- val appMicrophone =
- PrivacyItem(PrivacyType.TYPE_MICROPHONE,
- PrivacyApplication("Microphone", TEST_UID, context))
- val appLocation =
- PrivacyItem(PrivacyType.TYPE_LOCATION,
- PrivacyApplication("Location", TEST_UID, context))
-
- val items = listOf(appLocation, appMicrophone, appCamera)
- val textBuilder = PrivacyDialogBuilder(context, items)
- val appList = textBuilder.appsAndTypes.map { it.first }.map { it.packageName }
- assertEquals(listOf("Camera", "Microphone", "Location"), appList)
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.privacy
-
-import android.app.ActivityManager
-import android.app.AppOpsManager
-import android.content.Context
-import android.content.Intent
-import android.content.pm.UserInfo
-import android.os.Handler
-import android.os.UserHandle
-import android.os.UserManager
-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.R
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.appops.AppOpItem
-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
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.ArgumentMatchers.anyList
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-@RunWithLooper
-class PrivacyItemControllerTest : SysuiTestCase() {
-
- companion object {
- val CURRENT_USER_ID = ActivityManager.getCurrentUser()
- val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
- const val SYSTEM_UID = 1000
- const val TEST_PACKAGE_NAME = "test"
- const val DEVICE_SERVICES_STRING = "Device services"
- const val TAG = "PrivacyItemControllerTest"
- fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
- }
-
- @Mock
- private lateinit var appOpsController: AppOpsController
- @Mock
- private lateinit var callback: PrivacyItemController.Callback
- @Mock
- private lateinit var userManager: UserManager
- @Captor
- private lateinit var argCaptor: ArgumentCaptor<List<PrivacyItem>>
- @Captor
- private lateinit var argCaptorCallback: ArgumentCaptor<AppOpsController.Callback>
-
- private lateinit var testableLooper: TestableLooper
- private lateinit var privacyItemController: PrivacyItemController
- private lateinit var handler: Handler
-
- fun PrivacyItemController(context: Context) =
- PrivacyItemController(context, appOpsController, handler, handler)
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- testableLooper = TestableLooper.get(this)
- handler = Handler(testableLooper.looper)
-
- appOpsController = mDependency.injectMockDependency(AppOpsController::class.java)
- mDependency.injectTestDependency(Dependency.BG_HANDLER, handler)
- mDependency.injectTestDependency(Dependency.MAIN_HANDLER, handler)
- 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 {
- id = CURRENT_USER_ID
- }
- })).`when`(userManager).getProfiles(anyInt())
-
- 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()
- verify(callback).privacyChanged(anyList())
- }
-
- @Test
- fun testSetListeningFalseByRemovingLastCallback() {
- privacyItemController.addCallback(callback)
- testableLooper.processAllMessages()
- verify(appOpsController, never()).removeCallback(any(IntArray::class.java),
- any(AppOpsController.Callback::class.java))
- privacyItemController.removeCallback(callback)
- testableLooper.processAllMessages()
- verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
- any(AppOpsController.Callback::class.java))
- verify(callback).privacyChanged(emptyList())
- }
-
- @Test
- fun testDistinctItems() {
- doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
- AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
- privacyItemController.addCallback(callback)
- testableLooper.processAllMessages()
- verify(callback).privacyChanged(capture(argCaptor))
- assertEquals(1, argCaptor.value.size)
- }
-
- @Test
- fun testSystemApps() {
- doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, SYSTEM_UID, TEST_PACKAGE_NAME,
- 0))).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
- privacyItemController.addCallback(callback)
- testableLooper.processAllMessages()
- verify(callback).privacyChanged(capture(argCaptor))
- assertEquals(1, argCaptor.value.size)
- assertEquals(context.getString(R.string.device_services),
- argCaptor.value[0].application.applicationName)
- }
-
- @Test
- fun testRegisterReceiver_allUsers() {
- val spiedContext = spy(mContext)
- val itemController = PrivacyItemController(spiedContext)
- itemController.addCallback(callback)
- testableLooper.processAllMessages()
- verify(spiedContext, atLeastOnce()).registerReceiverAsUser(
- eq(itemController.userSwitcherReceiver), eq(UserHandle.ALL), any(), eq(null),
- eq(null))
- verify(spiedContext, never()).unregisterReceiver(eq(itemController.userSwitcherReceiver))
- }
-
- @Test
- fun testReceiver_ACTION_USER_FOREGROUND() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_USER_FOREGROUND))
- verify(userManager).getProfiles(anyInt())
- }
-
- @Test
- fun testReceiver_ACTION_MANAGED_PROFILE_ADDED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_ADDED))
- verify(userManager).getProfiles(anyInt())
- }
-
- @Test
- fun testReceiver_ACTION_MANAGED_PROFILE_REMOVED() {
- privacyItemController.userSwitcherReceiver.onReceive(context,
- Intent(Intent.ACTION_MANAGED_PROFILE_REMOVED))
- verify(userManager).getProfiles(anyInt())
- }
-
- @Test
- fun testAddMultipleCallbacks() {
- val otherCallback = mock(PrivacyItemController.Callback::class.java)
- privacyItemController.addCallback(callback)
- testableLooper.processAllMessages()
- verify(callback).privacyChanged(anyList())
-
- privacyItemController.addCallback(otherCallback)
- testableLooper.processAllMessages()
- verify(otherCallback).privacyChanged(anyList())
- // Adding a callback should not unnecessarily call previous ones
- verifyNoMoreInteractions(callback)
- }
-
- @Test
- fun testMultipleCallbacksAreUpdated() {
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
-
- val otherCallback = mock(PrivacyItemController.Callback::class.java)
- privacyItemController.addCallback(callback)
- privacyItemController.addCallback(otherCallback)
- testableLooper.processAllMessages()
- reset(callback)
- reset(otherCallback)
-
- verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
- argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
- testableLooper.processAllMessages()
- verify(callback).privacyChanged(anyList())
- verify(otherCallback).privacyChanged(anyList())
- }
-
- @Test
- fun testRemoveCallback() {
- doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
- val otherCallback = mock(PrivacyItemController.Callback::class.java)
- privacyItemController.addCallback(callback)
- privacyItemController.addCallback(otherCallback)
- testableLooper.processAllMessages()
- reset(callback)
- reset(otherCallback)
-
- verify(appOpsController).addCallback(any<IntArray>(), capture(argCaptorCallback))
- privacyItemController.removeCallback(callback)
- argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
- testableLooper.processAllMessages()
- verify(callback, never()).privacyChanged(anyList())
- verify(otherCallback).privacyChanged(anyList())
- }
-
- @Test
- fun testListShouldNotHaveNull() {
- doReturn(listOf(AppOpItem(AppOpsManager.OP_ACTIVATE_VPN, TEST_UID, "", 0),
- AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
- .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
- privacyItemController.addCallback(callback)
- testableLooper.processAllMessages()
-
- verify(callback).privacyChanged(capture(argCaptor))
- assertEquals(1, argCaptor.value.size)
- assertThat(argCaptor.value, not(hasItem(nullValue())))
- }
-
- @Test
- fun testListShouldBeCopy() {
- val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA,
- PrivacyApplication("", TEST_UID, mContext)))
- privacyItemController.privacyList = list
- val privacyList = privacyItemController.privacyList
- 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