DO NOT MERGE Remove Privacy Indicators
am: 9a560e068d
Change-Id: Ied3a51b9c47255a21e2fd29becfe4ed3400a16c7
This commit is contained in:
@@ -106,13 +106,6 @@ 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";
|
||||
|
||||
// Flags related to Assistant Handles
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,41 +0,0 @@
|
||||
<?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>
|
||||
@@ -1,24 +0,0 @@
|
||||
<?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"
|
||||
/>
|
||||
@@ -22,19 +22,11 @@
|
||||
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"
|
||||
@@ -46,23 +38,4 @@
|
||||
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>
|
||||
|
||||
@@ -48,7 +48,6 @@ import com.android.systemui.plugins.VolumeDialogController;
|
||||
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;
|
||||
@@ -287,7 +286,6 @@ public class Dependency extends SystemUI {
|
||||
@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;
|
||||
@@ -472,7 +470,6 @@ public class Dependency extends SystemUI {
|
||||
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);
|
||||
|
||||
@@ -20,7 +20,6 @@ import static com.android.systemui.Dependency.BG_LOOPER_NAME;
|
||||
|
||||
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;
|
||||
@@ -210,59 +209,6 @@ public class AppOpsControllerImpl implements AppOpsController,
|
||||
mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -286,8 +232,8 @@ public class AppOpsControllerImpl implements AppOpsController,
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -296,8 +242,8 @@ public class AppOpsControllerImpl implements AppOpsController,
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -323,8 +269,7 @@ public class AppOpsControllerImpl implements AppOpsController,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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)"
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
/*
|
||||
* 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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,30 +32,24 @@ import android.graphics.Color;
|
||||
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;
|
||||
@@ -63,11 +57,6 @@ import com.android.systemui.R;
|
||||
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;
|
||||
@@ -78,8 +67,6 @@ import com.android.systemui.statusbar.policy.DateView;
|
||||
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;
|
||||
|
||||
@@ -121,7 +108,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
private TintedIconManager mIconManager;
|
||||
private TouchAnimator mStatusIconsAlphaAnimator;
|
||||
private TouchAnimator mHeaderTextContainerAlphaAnimator;
|
||||
private TouchAnimator mPrivacyChipAlphaAnimator;
|
||||
private DualToneHandler mDualToneHandler;
|
||||
|
||||
private View mSystemIconsView;
|
||||
@@ -141,12 +127,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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
|
||||
@@ -156,41 +137,17 @@ 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) {
|
||||
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));
|
||||
}
|
||||
@@ -203,8 +160,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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);
|
||||
|
||||
@@ -218,9 +173,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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);
|
||||
|
||||
|
||||
@@ -243,7 +195,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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);
|
||||
@@ -254,26 +205,6 @@ 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() {
|
||||
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() {
|
||||
@@ -287,21 +218,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -408,7 +324,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
|
||||
updateStatusIconAlphaAnimator();
|
||||
updateHeaderTextContainerAlphaAnimator();
|
||||
updatePrivacyChipAlphaAnimator();
|
||||
}
|
||||
|
||||
private void updateStatusIconAlphaAnimator() {
|
||||
@@ -423,12 +338,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
.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;
|
||||
@@ -467,10 +376,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
mHeaderTextContainerView.setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
if (mPrivacyChipAlphaAnimator != null) {
|
||||
mPrivacyChip.setExpanded(expansionFraction > 0.5);
|
||||
mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
|
||||
}
|
||||
}
|
||||
|
||||
public void disable(int state1, int state2, boolean animate) {
|
||||
@@ -503,21 +408,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -542,13 +432,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,18 +453,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
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);
|
||||
|
||||
@@ -42,10 +42,6 @@ import com.android.systemui.Dependency;
|
||||
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;
|
||||
@@ -66,9 +62,6 @@ import com.android.systemui.statusbar.policy.SensorPrivacyController;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -83,12 +76,12 @@ public class PhoneStatusBarPolicy
|
||||
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;
|
||||
@@ -102,8 +95,6 @@ public class PhoneStatusBarPolicy
|
||||
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;
|
||||
@@ -121,7 +112,6 @@ public class PhoneStatusBarPolicy
|
||||
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;
|
||||
|
||||
@@ -154,7 +144,6 @@ public class PhoneStatusBarPolicy
|
||||
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);
|
||||
@@ -170,8 +159,6 @@ public class PhoneStatusBarPolicy
|
||||
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
|
||||
@@ -231,13 +218,6 @@ public class PhoneStatusBarPolicy
|
||||
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);
|
||||
@@ -257,7 +237,6 @@ public class PhoneStatusBarPolicy
|
||||
mNextAlarmController.addCallback(mNextAlarmCallback);
|
||||
mDataSaver.addCallback(this);
|
||||
mKeyguardMonitor.addCallback(this);
|
||||
mPrivacyItemController.addCallback(this);
|
||||
mSensorPrivacyController.addCallback(mSensorPrivacyListener);
|
||||
mLocationController.addCallback(this);
|
||||
|
||||
@@ -601,46 +580,9 @@ public class PhoneStatusBarPolicy
|
||||
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.
|
||||
|
||||
@@ -25,11 +25,9 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
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;
|
||||
@@ -55,7 +53,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
|
||||
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;
|
||||
@@ -74,18 +71,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -177,14 +162,6 @@ public class AppOpsControllerTest extends SysuiTestCase {
|
||||
mController.getActiveAppOpsForUser(UserHandle.getUserId(TEST_UID_OTHER)).size());
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user