| /* |
| * 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.model; |
| |
| import static android.Manifest.permission_group.MICROPHONE; |
| |
| import android.app.AppOpsManager; |
| import android.app.AppOpsManager.HistoricalOp; |
| import android.app.AppOpsManager.HistoricalPackageOps; |
| import android.app.AppOpsManager.OpEntry; |
| import android.app.AppOpsManager.PackageOps; |
| import android.media.AudioRecordingConfiguration; |
| import android.util.Pair; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.Function; |
| |
| /** |
| * Stats for permission usage of an app. This data is for a given time period, |
| * i.e. does not contain the full history. |
| */ |
| public final class AppPermissionUsage { |
| private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>(); |
| private final @NonNull PermissionApp mPermissionApp; |
| |
| private AppPermissionUsage(@NonNull PermissionApp permissionApp, |
| @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage, |
| @Nullable HistoricalPackageOps historicalUsage, |
| @Nullable ArrayList<AudioRecordingConfiguration> recordings) { |
| mPermissionApp = permissionApp; |
| final int groupCount = groups.size(); |
| for (int i = 0; i < groupCount; i++) { |
| final AppPermissionGroup group = groups.get(i); |
| |
| /** |
| * TODO: HACK HACK HACK. |
| * |
| * Exclude for the UIDs that are currently silenced. This happens if an app keeps |
| * recording while in the background for more than a few seconds. |
| */ |
| if (recordings != null && group.getName().equals(MICROPHONE)) { |
| boolean isSilenced = false; |
| int recordingsCount = recordings.size(); |
| for (int recordingNum = 0; recordingNum < recordingsCount; recordingNum++) { |
| AudioRecordingConfiguration recording = recordings.get(recordingNum); |
| if (recording.isClientSilenced()) { |
| isSilenced = true; |
| break; |
| } |
| } |
| |
| if (isSilenced) { |
| continue; |
| } |
| } |
| |
| mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage)); |
| } |
| } |
| |
| public @NonNull PermissionApp getApp() { |
| return mPermissionApp; |
| } |
| |
| public @NonNull String getPackageName() { |
| return mPermissionApp.getPackageName(); |
| } |
| |
| public int getUid() { |
| return mPermissionApp.getUid(); |
| } |
| |
| public long getLastAccessTime() { |
| long lastAccessTime = 0; |
| final int permissionCount = mGroupUsages.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final GroupUsage groupUsage = mGroupUsages.get(i); |
| lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime()); |
| } |
| return lastAccessTime; |
| } |
| |
| public long getAccessCount() { |
| long accessCount = 0; |
| final int permissionCount = mGroupUsages.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final GroupUsage permission = mGroupUsages.get(i); |
| accessCount += permission.getAccessCount(); |
| } |
| return accessCount; |
| } |
| |
| public @NonNull List<GroupUsage> getGroupUsages() { |
| return mGroupUsages; |
| } |
| |
| /** |
| * Stats for permission usage of a permission group. This data is for a |
| * given time period, i.e. does not contain the full history. |
| */ |
| public static class GroupUsage { |
| private final @NonNull AppPermissionGroup mGroup; |
| private final @Nullable PackageOps mLastUsage; |
| private final @Nullable HistoricalPackageOps mHistoricalUsage; |
| |
| public GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage, |
| @Nullable HistoricalPackageOps historicalUsage) { |
| mGroup = group; |
| mLastUsage = lastUsage; |
| mHistoricalUsage = historicalUsage; |
| } |
| |
| public long getLastAccessTime() { |
| if (mLastUsage == null) { |
| return 0; |
| } |
| return lastAccessAggregate( |
| (op) -> op.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| public long getLastAccessForegroundTime() { |
| if (mLastUsage == null) { |
| return 0; |
| } |
| return lastAccessAggregate( |
| (op) -> op.getLastAccessForegroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| public long getLastAccessBackgroundTime() { |
| if (mLastUsage == null) { |
| return 0; |
| } |
| return lastAccessAggregate( |
| (op) -> op.getLastAccessBackgroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| public long getForegroundAccessCount() { |
| if (mHistoricalUsage == null) { |
| return 0; |
| } |
| return extractAggregate((HistoricalOp op) |
| -> op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| public long getBackgroundAccessCount() { |
| if (mHistoricalUsage == null) { |
| return 0; |
| } |
| return extractAggregate((HistoricalOp op) |
| -> op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| public long getAccessCount() { |
| if (mHistoricalUsage == null) { |
| return 0; |
| } |
| return extractAggregate((HistoricalOp op) -> |
| op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED) |
| + op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED) |
| ); |
| } |
| |
| /** |
| * returns whether the usage has discrete data |
| */ |
| public boolean hasDiscreteData() { |
| if (mHistoricalUsage == null) { |
| return false; |
| } |
| |
| final ArrayList<Permission> permissions = mGroup.getPermissions(); |
| final int permissionCount = permissions.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final Permission permission = permissions.get(i); |
| final String opName = permission.getAppOp(); |
| final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); |
| if (historicalOp != null && historicalOp.getDiscreteAccessCount() > 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * get all discrete access time in millis |
| * Returns a list of pairs of (access time, access duration) |
| */ |
| public List<Pair<Long, Long>> getAllDiscreteAccessTime() { |
| List<Pair<Long, Long>> allDiscreteAccessTime = new ArrayList<>(); |
| if (!hasDiscreteData()) { |
| return allDiscreteAccessTime; |
| } |
| |
| final ArrayList<Permission> permissions = mGroup.getPermissions(); |
| final int permissionCount = permissions.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final Permission permission = permissions.get(i); |
| final String opName = permission.getAppOp(); |
| final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); |
| if (historicalOp == null) { |
| continue; |
| } |
| |
| int discreteAccessCount = historicalOp.getDiscreteAccessCount(); |
| for (int j = 0; j < discreteAccessCount; j++) { |
| AppOpsManager.AttributedOpEntry opEntry = historicalOp.getDiscreteAccessAt(j); |
| int flags = AppOpsManager.OP_FLAG_SELF | AppOpsManager.OP_FLAG_TRUSTED_PROXIED; |
| allDiscreteAccessTime.add(Pair.create(opEntry.getLastAccessTime(flags), |
| opEntry.getLastDuration(flags))); |
| } |
| |
| } |
| |
| return allDiscreteAccessTime; |
| } |
| |
| public long getLastAccessDuration() { |
| if (mLastUsage == null) { |
| return 0; |
| } |
| return lastAccessAggregate( |
| (op) -> op.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)); |
| } |
| |
| |
| public long getAccessDuration() { |
| if (mHistoricalUsage == null) { |
| return 0; |
| } |
| return extractAggregate((HistoricalOp op) -> |
| op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED) |
| + op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED) |
| ); |
| } |
| |
| public boolean isRunning() { |
| if (mLastUsage == null) { |
| return false; |
| } |
| final ArrayList<Permission> permissions = mGroup.getPermissions(); |
| final int permissionCount = permissions.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final Permission permission = permissions.get(i); |
| final String opName = permission.getAppOp(); |
| final List<OpEntry> ops = mLastUsage.getOps(); |
| final int opCount = ops.size(); |
| for (int j = 0; j < opCount; j++) { |
| final OpEntry op = ops.get(j); |
| if (op.getOpStr().equals(opName) && op.isRunning()) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) { |
| long aggregate = 0; |
| final ArrayList<Permission> permissions = mGroup.getPermissions(); |
| final int permissionCount = permissions.size(); |
| for (int i = 0; i < permissionCount; i++) { |
| final Permission permission = permissions.get(i); |
| final String opName = permission.getAppOp(); |
| final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName); |
| if (historicalOp != null) { |
| aggregate += extractor.apply(historicalOp); |
| } |
| } |
| return aggregate; |
| } |
| |
| private long lastAccessAggregate(@NonNull Function<OpEntry, Long> extractor) { |
| long aggregate = 0; |
| final ArrayList<Permission> permissions = mGroup.getPermissions(); |
| final int permissionCount = permissions.size(); |
| for (int permissionNum = 0; permissionNum < permissionCount; permissionNum++) { |
| final Permission permission = permissions.get(permissionNum); |
| final String opName = permission.getAppOp(); |
| final List<OpEntry> ops = mLastUsage.getOps(); |
| final int opCount = ops.size(); |
| for (int opNum = 0; opNum < opCount; opNum++) { |
| final OpEntry op = ops.get(opNum); |
| if (op.getOpStr().equals(opName)) { |
| aggregate = Math.max(aggregate, extractor.apply(op)); |
| } |
| } |
| } |
| return aggregate; |
| } |
| |
| public @NonNull AppPermissionGroup getGroup() { |
| return mGroup; |
| } |
| |
| /** |
| * Returns attribution tags for the historical usage. |
| */ |
| public @Nullable ArrayList<String> getAttributionTags() { |
| if (mHistoricalUsage == null || mHistoricalUsage.getAttributedOpsCount() == 0) { |
| return null; |
| } |
| ArrayList<String> attributionTags = new ArrayList<>(); |
| int count = mHistoricalUsage.getAttributedOpsCount(); |
| for (int i = 0; i < count; i++) { |
| attributionTags.add(mHistoricalUsage.getAttributedOpsAt(i).getTag()); |
| } |
| return attributionTags; |
| } |
| } |
| |
| public static class Builder { |
| private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>(); |
| private final @NonNull PermissionApp mPermissionApp; |
| private @Nullable PackageOps mLastUsage; |
| private @Nullable HistoricalPackageOps mHistoricalUsage; |
| private @Nullable ArrayList<AudioRecordingConfiguration> mAudioRecordingConfigurations; |
| |
| public Builder(@NonNull PermissionApp permissionApp) { |
| mPermissionApp = permissionApp; |
| } |
| |
| public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) { |
| mGroups.add(group); |
| return this; |
| } |
| |
| public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) { |
| mLastUsage = lastUsage; |
| return this; |
| } |
| |
| public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) { |
| mHistoricalUsage = historicalUsage; |
| return this; |
| } |
| |
| public @NonNull Builder setRecordingConfiguration( |
| @Nullable ArrayList<AudioRecordingConfiguration> recordings) { |
| mAudioRecordingConfigurations = recordings; |
| return this; |
| } |
| |
| public @NonNull AppPermissionUsage build() { |
| if (mGroups.isEmpty()) { |
| throw new IllegalStateException("mGroups cannot be empty."); |
| } |
| return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage, |
| mAudioRecordingConfigurations); |
| } |
| } |
| } |