summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip P. Moltmann <moltmann@google.com>2020-08-20 19:30:31 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-08-20 19:30:31 +0000
commit4b867486850c75ae98f07525df6558f6c9063295 (patch)
tree7701594f15818200c3e48662369c5eb2bf2efa7a
parent8c21a4b602babcc1bdb20d70b8141e66c79264d6 (diff)
parentfa51a6e20487c3cf4e552614eba47e9e5be70446 (diff)
downloadPackageInstaller-4b867486850c75ae98f07525df6558f6c9063295.tar.gz
Merge changes from topic "phone-call-indicators" into rvc-qpr-dev
* changes: Show some limited system usage in ongoing usage dialog Update to latests UI mocks. Listen to real app-ops + small bug fixes Special phone call handling in ReivewOngoingUsage
-rw-r--r--res/layout/ongoing_usage_dialog_content.xml17
-rw-r--r--res/values/overlayable.xml4
-rw-r--r--res/values/strings.xml17
-rw-r--r--res/values/styles.xml26
-rw-r--r--src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt127
-rw-r--r--src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java111
6 files changed, 295 insertions, 7 deletions
diff --git a/res/layout/ongoing_usage_dialog_content.xml b/res/layout/ongoing_usage_dialog_content.xml
index 219b4f9c0..f9a0f07f0 100644
--- a/res/layout/ongoing_usage_dialog_content.xml
+++ b/res/layout/ongoing_usage_dialog_content.xml
@@ -35,6 +35,23 @@
android:id="@+id/items_container"
style="@style/PermissionUsageDialogItemsContainer"/>
+ <TextView
+ android:id="@+id/other_use_header"
+ android:text="@string/other_use"
+ style="@style/PermissionUsageDialogOtherUseHeader"/>
+
+ <TextView
+ android:id="@+id/other_use_content"
+ style="@style/PermissionUsageDialogOtherUseContent"/>
+
+ <View
+ android:id="@+id/other_use_inside_spacer"
+ style="@style/PermissionUsageDialogOtherUseInsideSpacer"/>
+
+ <TextView
+ android:id="@+id/system_use_content"
+ style="@style/PermissionUsageDialogSystemUseContent"/>
+
</LinearLayout>
</ScrollView>
diff --git a/res/values/overlayable.xml b/res/values/overlayable.xml
index 162744295..aa683a43e 100644
--- a/res/values/overlayable.xml
+++ b/res/values/overlayable.xml
@@ -153,6 +153,10 @@
<item type="style" name="PermissionUsageDialogItemAppName" />
<item type="style" name="PermissionUsageDialogItemPermissionsList" />
<item type="style" name="PermissionUsageDialogItemIconsContainer" />
+ <item type="style" name="PermissionUsageDialogOtherUseHeader" />
+ <item type="style" name="PermissionUsageDialogOtherUseContent" />
+ <item type="style" name="PermissionUsageDialogOtherUseInsideSpacer" />
+ <item type="style" name="PermissionUsageDialogSystemUseContent" />
<!-- END ONGOING USAGE DIALOG -->
<!-- START REQUEST ROLE DIALOG TITLE -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 490817a2f..e7804ffb2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -884,6 +884,23 @@
<!-- Label for the button to set an application as the default application [CHAR LIMIT=20] -->
<string name="request_role_set_as_default">Set as default</string>
+ <!-- Message telling the user that a phone call is currently using the microphone [CHAR LIMIT=none] -->
+ <string name="phone_call_uses_microphone">Microphone is used in &lt;b>phone call&lt;/b></string>
+ <!-- Message telling the user that a phone call is currently using the microphone and the camera [CHAR LIMIT=none] -->
+ <string name="phone_call_uses_microphone_and_camera">Camera and Microphone are used in &lt;b>video call&lt;/b></string>
+ <!-- Message telling the user that a phone call is currently using the camera [CHAR LIMIT=none] -->
+ <string name="phone_call_uses_camera">Camera is used in &lt;b>video call&lt;/b></string>
+
+ <!-- Message telling the user that a system service is currently using the microphone [CHAR LIMIT=none] -->
+ <string name="system_uses_microphone">Microphone is accessed using system service</string>
+ <!-- Message telling the user that a system service is currently using the microphone and the camera [CHAR LIMIT=none] -->
+ <string name="system_uses_microphone_and_camera">Camera and Microphone are accessed using system service</string>
+ <!-- Message telling the user that a system service is currently using the camera [CHAR LIMIT=none] -->
+ <string name="system_uses_camera">Camera is accessed using system service</string>
+
+ <!-- Line above a list of other apps and system service that are currently microphone or camera [CHAR LIMIT=60] -->
+ <string name="other_use">Other use:</string>
+
<!-- Action for accepting the Ongoing usage dialog [CHAR LIMIT=10]-->
<string name="ongoing_usage_dialog_ok">Got it</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 496e95cb9..c1c8e2d2f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -663,6 +663,32 @@
<item name="android:layout_gravity">end</item>
</style>
+ <style name="PermissionUsageDialogOtherUseHeader">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
+ <style name="PermissionUsageDialogOtherUseContent">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
+ <style name="PermissionUsageDialogOtherUseInsideSpacer">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">16dp</item>
+ </style>
+
+ <style name="PermissionUsageDialogSystemUseContent">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:textAppearanceListItemSecondary</item>
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
<!-- END ONGOING USAGE DIALOG -->
<!-- START REQUEST ROLE DIALOG TITLE -->
diff --git a/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt b/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt
new file mode 100644
index 000000000..5101dd7ee
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/data/OpUsageLiveData.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 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.permissioncontroller.permission.data
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED
+import android.app.Application
+import android.os.UserHandle
+import com.android.permissioncontroller.PermissionControllerApplication
+import kotlinx.coroutines.Job
+import java.util.function.Consumer
+
+/**
+ * LiveData that loads the last usage of each of a list of app ops for every package.
+ *
+ * <p>For app-ops with duration the end of the access is considered.
+ *
+ * @param app The current application
+ * @param opNames The names of the app ops we wish to search for
+ * @param usageDurationMs how much ago can an access have happened to be considered
+ */
+// TODO: listen for updates
+class OpUsageLiveData(
+ private val app: Application,
+ private val opNames: List<String>,
+ private val usageDurationMs: Long
+) : SmartAsyncMediatorLiveData<@JvmSuppressWildcards Map<String, List<OpAccess>>>(),
+ Consumer<AppOpsManager.HistoricalOps> {
+ val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ val now = System.currentTimeMillis()
+ val opMap = mutableMapOf<String, MutableList<OpAccess>>()
+
+ val packageOps = appOpsManager.getPackagesForOps(opNames.toTypedArray())
+ for (packageOp in packageOps) {
+ for (opEntry in packageOp.ops) {
+ val user = UserHandle.getUserHandleForUid(packageOp.uid)
+ val lastAccessTime: Long = opEntry.getLastAccessTime(OP_FLAGS_ALL_TRUSTED)
+
+ if (lastAccessTime == -1L) {
+ // There was no access, so skip
+ continue
+ }
+
+ var lastAccessDuration = opEntry.getLastDuration(OP_FLAGS_ALL_TRUSTED)
+
+ // Some accesses have no duration
+ if (lastAccessDuration == -1L) {
+ lastAccessDuration = 0
+ }
+
+ if (opEntry.isRunning ||
+ lastAccessTime + lastAccessDuration > (now - usageDurationMs)) {
+ val accessList = opMap.getOrPut(opEntry.opStr) { mutableListOf() }
+ val accessTime = if (opEntry.isRunning) {
+ -1
+ } else {
+ lastAccessTime
+ }
+ accessList.add(OpAccess(packageOp.packageName, user, accessTime))
+ }
+ }
+ }
+
+ postValue(opMap)
+ }
+
+ override fun accept(historicalOps: AppOpsManager.HistoricalOps) {
+ val opMap = mutableMapOf<String, MutableList<OpAccess>>()
+ for (i in 0 until historicalOps.uidCount) {
+ val historicalUidOps = historicalOps.getUidOpsAt(i)
+ val user = UserHandle.getUserHandleForUid(historicalUidOps.uid)
+ for (j in 0 until historicalUidOps.packageCount) {
+ val historicalPkgOps = historicalUidOps.getPackageOpsAt(j)
+ val pkgName = historicalPkgOps.packageName
+ for (k in 0 until historicalPkgOps.opCount) {
+ val historicalAttributedOps = historicalPkgOps.getAttributedOpsAt(k)
+ for (l in 0 until historicalAttributedOps.opCount) {
+ val historicalOp = historicalAttributedOps.getOpAt(l)
+ val opName = historicalOp.opName
+
+ val accessList = opMap.getOrPut(opName) { mutableListOf() }
+ accessList.add(OpAccess(pkgName, user, -1))
+ }
+ }
+ }
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+ updateAsync()
+ }
+
+ companion object : DataRepository<Pair<List<String>, Long>, OpUsageLiveData>() {
+ override fun newValue(key: Pair<List<String>, Long>): OpUsageLiveData {
+ return OpUsageLiveData(PermissionControllerApplication.get(), key.first, key.second)
+ }
+
+ operator fun get(ops: List<String>, usageDurationMs: Long): OpUsageLiveData {
+ return get(ops to usageDurationMs)
+ }
+ }
+}
+
+data class OpAccess(val packageName: String?, val user: UserHandle?, val lastAccessTime: Long) {
+ companion object {
+ const val IS_RUNNING = -1L
+ }
+
+ fun isRunning() = lastAccessTime == IS_RUNNING
+}
diff --git a/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java b/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
index 89e804c16..41b8a21b8 100644
--- a/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
+++ b/src/com/android/permissioncontroller/permission/ui/handheld/ReviewOngoingUsageFragment.java
@@ -28,9 +28,12 @@ import static com.android.permissioncontroller.permission.debug.UtilsKt.shouldSh
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
+import android.location.LocationManager;
import android.os.Bundle;
import android.os.UserHandle;
+import android.text.Html;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
@@ -40,30 +43,43 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.Observer;
import androidx.preference.PreferenceFragmentCompat;
import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.data.OpAccess;
+import com.android.permissioncontroller.permission.data.OpUsageLiveData;
import com.android.permissioncontroller.permission.debug.PermissionUsages;
+import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.AppPermissionUsage;
import com.android.permissioncontroller.permission.model.AppPermissionUsage.GroupUsage;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import java.text.Collator;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
/**
* A dialog listing the currently uses of camera, microphone, and location.
*/
public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
+ // TODO: Replace with OPSTR... APIs
+ static final String PHONE_CALL = "android:phone_call_microphone";
+ static final String VIDEO_CALL = "android:phone_call_camera";
+
private @NonNull PermissionUsages mPermissionUsages;
+ private boolean mPermissionUsagesLoaded;
private @Nullable AlertDialog mDialog;
+ private OpUsageLiveData mOpUsageLiveData;
+ private @Nullable Map<String, List<OpAccess>> mOpUsage;
+ private ArraySet<String> mSystemUsage = new ArraySet<>(0);
private long mStartTime;
/**
@@ -89,13 +105,27 @@ public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
if (shouldShowPermissionsDashboard()) {
permissions = new String[] {CAMERA, LOCATION, MICROPHONE};
}
+ ArrayList<String> appOps = new ArrayList<>(List.of(PHONE_CALL, VIDEO_CALL));
+ mOpUsageLiveData = OpUsageLiveData.Companion.get(appOps, numMillis);
+ mOpUsageLiveData.observe(this, new Observer<Map<String, List<OpAccess>>>() {
+ @Override
+ public void onChanged(Map<String, List<OpAccess>> opUsage) {
+ mOpUsage = opUsage;
+ mOpUsageLiveData.removeObserver(this);
+
+ if (mPermissionUsagesLoaded) {
+ onPermissionUsagesLoaded();
+ }
+ }
+ });
mPermissionUsages.load(null, permissions, mStartTime, Long.MAX_VALUE,
PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(), false, false,
this::onPermissionUsagesLoaded, false);
}
private void onPermissionUsagesLoaded() {
- if (getActivity() == null) {
+ mPermissionUsagesLoaded = true;
+ if (getActivity() == null || mOpUsage == null) {
return;
}
@@ -130,11 +160,13 @@ public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
}
}
- if (!Utils.isGroupOrBgGroupUserSensitive(groupUsage.getGroup())) {
- continue;
+ if (Utils.isGroupOrBgGroupUserSensitive(groupUsage.getGroup())) {
+ usedGroups.add(appGroups.get(groupNum));
+ } else if (getContext().getSystemService(LocationManager.class).isProviderPackage(
+ appUsage.getPackageName())
+ && (groupName.equals(CAMERA) || groupName.equals(MICROPHONE))) {
+ mSystemUsage.add(groupName);
}
-
- usedGroups.add(appGroups.get(groupNum));
}
if (!usedGroups.isEmpty()) {
@@ -143,7 +175,7 @@ public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
}
}
- if (usages.isEmpty()) {
+ if (usages.isEmpty() && mOpUsage.isEmpty() && mSystemUsage.isEmpty()) {
getActivity().finish();
return;
}
@@ -220,6 +252,71 @@ public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
}
}
+ TextView otherUseHeader = contentView.requireViewById(R.id.other_use_header);
+ TextView otherUseContent = contentView.requireViewById(R.id.other_use_content);
+ TextView systemUseContent = contentView.requireViewById(R.id.system_use_content);
+ View otherUseSpacer = contentView.requireViewById(R.id.other_use_inside_spacer);
+
+ if (mOpUsage.isEmpty() && mSystemUsage.isEmpty()) {
+ otherUseHeader.setVisibility(View.GONE);
+ otherUseContent.setVisibility(View.GONE);
+ }
+
+ if (numUsages == 0) {
+ otherUseHeader.setVisibility(View.GONE);
+ appsList.setVisibility(View.GONE);
+ }
+
+ if (mOpUsage.isEmpty() || mSystemUsage.isEmpty()) {
+ otherUseSpacer.setVisibility(View.GONE);
+ }
+
+ if (mOpUsage.isEmpty()) {
+ otherUseContent.setVisibility(View.GONE);
+ }
+
+ if (mSystemUsage.isEmpty()) {
+ systemUseContent.setVisibility(View.GONE);
+ }
+
+ if (!mOpUsage.isEmpty()) {
+ if (mOpUsage.containsKey(VIDEO_CALL) && mOpUsage.containsKey(
+ PHONE_CALL)) {
+ otherUseContent.setText(
+ Html.fromHtml(getString(R.string.phone_call_uses_microphone_and_camera),
+ 0));
+ } else if (mOpUsage.containsKey(VIDEO_CALL)) {
+ otherUseContent.setText(
+ Html.fromHtml(getString(R.string.phone_call_uses_camera), 0));
+ } else if (mOpUsage.containsKey(PHONE_CALL)) {
+ otherUseContent.setText(
+ Html.fromHtml(getString(R.string.phone_call_uses_microphone), 0));
+ }
+
+ if (mOpUsage.containsKey(VIDEO_CALL)) {
+ usedGroups.put(CAMERA, KotlinUtils.INSTANCE.getPermGroupLabel(context, CAMERA));
+ }
+
+ if (mOpUsage.containsKey(PHONE_CALL)) {
+ usedGroups.put(MICROPHONE,
+ KotlinUtils.INSTANCE.getPermGroupLabel(context, MICROPHONE));
+ }
+ }
+
+ if (!mSystemUsage.isEmpty()) {
+ if (mSystemUsage.contains(MICROPHONE) && mSystemUsage.contains(CAMERA)) {
+ systemUseContent.setText(getString(R.string.system_uses_microphone_and_camera));
+ } else if (mSystemUsage.contains(CAMERA)) {
+ systemUseContent.setText(getString(R.string.system_uses_camera));
+ } else if (mSystemUsage.contains(MICROPHONE) ) {
+ systemUseContent.setText(getString(R.string.system_uses_microphone));
+ }
+
+ for (String usage : mSystemUsage) {
+ usedGroups.put(usage, KotlinUtils.INSTANCE.getPermGroupLabel(context, usage));
+ }
+ }
+
// Add the layout for each app.
for (int usageNum = 0; usageNum < numUsages; usageNum++) {
Pair<AppPermissionUsage, List<GroupUsage>> usage = usages.get(usageNum);