Merge "Add API support for permission access timeline." into sc-dev

This commit is contained in:
Stanislav Zholnin
2021-03-03 16:35:28 +00:00
committed by Android (Google) Code Review
7 changed files with 1117 additions and 108 deletions

View File

@@ -423,6 +423,9 @@ package android.app {
method @Nullable public static String opToPermission(@NonNull String);
method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setMode(@NonNull String, int, @Nullable String, int);
method @RequiresPermission("android.permission.MANAGE_APP_OPS_MODES") public void setUidMode(@NonNull String, int, int);
field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2
field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
@@ -536,9 +539,14 @@ package android.app {
method public long getAccessDuration(int, int, int);
method public long getBackgroundAccessCount(int);
method public long getBackgroundAccessDuration(int);
method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getBackgroundDiscreteAccesses(int);
method public long getBackgroundRejectCount(int);
method @NonNull public android.app.AppOpsManager.AttributedOpEntry getDiscreteAccessAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getDiscreteAccessCount();
method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getDiscreteAccesses(int, int, int);
method public long getForegroundAccessCount(int);
method public long getForegroundAccessDuration(int);
method @NonNull public java.util.List<android.app.AppOpsManager.AttributedOpEntry> getForegroundDiscreteAccesses(int);
method public long getForegroundRejectCount(int);
method @NonNull public String getOpName();
method public long getRejectCount(int, int, int);
@@ -565,6 +573,7 @@ package android.app {
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setAttributionTag(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setHistoryFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setUid(int);

View File

@@ -224,6 +224,9 @@ package android.app {
field public static final int HISTORICAL_MODE_DISABLED = 0; // 0x0
field public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1; // 0x1
field public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2; // 0x2
field public static final int HISTORY_FLAGS_ALL = 3; // 0x3
field public static final int HISTORY_FLAG_AGGREGATE = 1; // 0x1
field public static final int HISTORY_FLAG_DISCRETE = 2; // 0x2
field public static final String KEY_BG_STATE_SETTLE_TIME = "bg_state_settle_time";
field public static final String KEY_FG_SERVICE_STATE_SETTLE_TIME = "fg_service_state_settle_time";
field public static final String KEY_TOP_STATE_SETTLE_TIME = "top_state_settle_time";
@@ -238,6 +241,7 @@ package android.app {
public static final class AppOpsManager.HistoricalOps implements android.os.Parcelable {
ctor public AppOpsManager.HistoricalOps(long, long);
method public void addDiscreteAccess(int, int, @NonNull String, @Nullable String, int, int, long, long);
method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long);
method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long);
method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long);

View File

@@ -16,6 +16,8 @@
package android.app;
import static java.lang.Long.max;
import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
@@ -3385,6 +3387,13 @@ public class AppOpsManager {
@DataClass.ParcelWith(LongSparseArrayParceling.class)
private final @Nullable LongSparseArray<NoteOpEvent> mRejectEvents;
private AttributedOpEntry(@NonNull AttributedOpEntry other) {
mOp = other.mOp;
mRunning = other.mRunning;
mAccessEvents = other.mAccessEvents == null ? null : other.mAccessEvents.clone();
mRejectEvents = other.mRejectEvents == null ? null : other.mRejectEvents.clone();
}
/**
* Returns all keys for which we have events.
*
@@ -3749,6 +3758,15 @@ public class AppOpsManager {
return lastEvent.getProxy();
}
@NonNull
String getOpName() {
return AppOpsManager.opToPublicName(mOp);
}
int getOp() {
return mOp;
}
private static class LongSparseArrayParceling implements
Parcelling<LongSparseArray<NoteOpEvent>> {
@Override
@@ -4570,6 +4588,50 @@ public class AppOpsManager {
void visitHistoricalOp(@NonNull HistoricalOp ops);
}
/**
* Flag for querying app op history: get only aggregate information and no
* discrete accesses.
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
* @hide
*/
@TestApi
@SystemApi
public static final int HISTORY_FLAG_AGGREGATE = 1 << 0;
/**
* Flag for querying app op history: get only discrete information and no
* aggregate accesses.
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
* @hide
*/
@TestApi
@SystemApi
public static final int HISTORY_FLAG_DISCRETE = 1 << 1;
/**
* Flag for querying app op history: get all types of historical accesses.
*
* @see #getHistoricalOps(HistoricalOpsRequest, Executor, Consumer)
*
* @hide
*/
@TestApi
@SystemApi
public static final int HISTORY_FLAGS_ALL = HISTORY_FLAG_AGGREGATE
| HISTORY_FLAG_DISCRETE;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "HISTORY_FLAG_" }, value = {
HISTORY_FLAG_AGGREGATE,
HISTORY_FLAG_DISCRETE
})
public @interface OpHistoryFlags {}
/**
* Specifies what parameters to filter historical appop requests for
*
@@ -4625,6 +4687,7 @@ public class AppOpsManager {
private final @Nullable String mPackageName;
private final @Nullable String mAttributionTag;
private final @Nullable List<String> mOpNames;
private final @OpHistoryFlags int mHistoryFlags;
private final @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
@@ -4632,12 +4695,13 @@ public class AppOpsManager {
private HistoricalOpsRequest(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable List<String> opNames,
@HistoricalOpsRequestFilter int filter, long beginTimeMillis,
long endTimeMillis, @OpFlags int flags) {
@OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis, @OpFlags int flags) {
mUid = uid;
mPackageName = packageName;
mAttributionTag = attributionTag;
mOpNames = opNames;
mHistoryFlags = historyFlags;
mFilter = filter;
mBeginTimeMillis = beginTimeMillis;
mEndTimeMillis = endTimeMillis;
@@ -4655,6 +4719,7 @@ public class AppOpsManager {
private @Nullable String mPackageName;
private @Nullable String mAttributionTag;
private @Nullable List<String> mOpNames;
private @OpHistoryFlags int mHistoryFlags;
private @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
@@ -4676,6 +4741,7 @@ public class AppOpsManager {
"beginTimeMillis must be non negative and lesser than endTimeMillis");
mBeginTimeMillis = beginTimeMillis;
mEndTimeMillis = endTimeMillis;
mHistoryFlags = HISTORY_FLAG_AGGREGATE;
}
/**
@@ -4771,12 +4837,26 @@ public class AppOpsManager {
return this;
}
/**
* Specifies what type of historical information to query.
*
* @param flags Flags for the historical types to fetch which are any
* combination of {@link #HISTORY_FLAG_AGGREGATE}, {@link #HISTORY_FLAG_DISCRETE},
* {@link #HISTORY_FLAGS_ALL}. The default is {@link #HISTORY_FLAG_AGGREGATE}.
* @return This builder.
*/
public @NonNull Builder setHistoryFlags(@OpHistoryFlags int flags) {
Preconditions.checkFlagsArgument(flags, HISTORY_FLAGS_ALL);
mHistoryFlags = flags;
return this;
}
/**
* @return a new {@link HistoricalOpsRequest}.
*/
public @NonNull HistoricalOpsRequest build() {
return new HistoricalOpsRequest(mUid, mPackageName, mAttributionTag, mOpNames,
mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
mHistoryFlags, mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
}
}
}
@@ -4943,7 +5023,8 @@ public class AppOpsManager {
* @hide
*/
public void filter(int uid, @Nullable String packageName, @Nullable String attributionTag,
@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
@Nullable String[] opNames, @OpHistoryFlags int historyFilter,
@HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis) {
final long durationMillis = getDurationMillis();
mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
@@ -4956,7 +5037,8 @@ public class AppOpsManager {
if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) {
mHistoricalUidOps.removeAt(i);
} else {
uidOp.filter(packageName, attributionTag, opNames, filter, scaleFactor);
uidOp.filter(packageName, attributionTag, opNames, filter, historyFilter,
scaleFactor, mBeginTimeMillis, mEndTimeMillis);
if (uidOp.getPackageCount() == 0) {
mHistoricalUidOps.removeAt(i);
}
@@ -5011,6 +5093,16 @@ public class AppOpsManager {
packageName, attributionTag, uidState, flags, increment);
}
/** @hide */
@TestApi
public void addDiscreteAccess(int opCode, int uid, @NonNull String packageName,
@Nullable String attributionTag, @UidState int uidState, @OpFlags int opFlag,
long discreteAccessTime, long discreteAccessDuration) {
getOrCreateHistoricalUidOps(uid).addDiscreteAccess(opCode, packageName, attributionTag,
uidState, opFlag, discreteAccessTime, discreteAccessDuration);
};
/** @hide */
@TestApi
public void offsetBeginAndEndTime(long offsetMillis) {
@@ -5288,7 +5380,8 @@ public class AppOpsManager {
private void filter(@Nullable String packageName, @Nullable String attributionTag,
@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
double fractionToRemove) {
@OpHistoryFlags int historyFilter, double fractionToRemove, long beginTimeMillis,
long endTimeMillis) {
final int packageCount = getPackageCount();
for (int i = packageCount - 1; i >= 0; i--) {
final HistoricalPackageOps packageOps = getPackageOpsAt(i);
@@ -5296,7 +5389,8 @@ public class AppOpsManager {
packageOps.getPackageName())) {
mHistoricalPackageOps.removeAt(i);
} else {
packageOps.filter(attributionTag, opNames, filter, fractionToRemove);
packageOps.filter(attributionTag, opNames, filter, historyFilter,
fractionToRemove, beginTimeMillis, endTimeMillis);
if (packageOps.getAttributedOpsCount() == 0) {
mHistoricalPackageOps.removeAt(i);
}
@@ -5336,6 +5430,13 @@ public class AppOpsManager {
opCode, attributionTag, uidState, flags, increment);
}
private void addDiscreteAccess(int opCode, @NonNull String packageName,
@Nullable String attributionTag, @UidState int uidState,
@OpFlags int flag, long discreteAccessTime, long discreteAccessDuration) {
getOrCreateHistoricalPackageOps(packageName).addDiscreteAccess(opCode, attributionTag,
uidState, flag, discreteAccessTime, discreteAccessDuration);
};
/**
* @return The UID for which the data is related.
*/
@@ -5540,7 +5641,8 @@ public class AppOpsManager {
}
private void filter(@Nullable String attributionTag, @Nullable String[] opNames,
@HistoricalOpsRequestFilter int filter, double fractionToRemove) {
@HistoricalOpsRequestFilter int filter, @OpHistoryFlags int historyFilter,
double fractionToRemove, long beginTimeMillis, long endTimeMillis) {
final int attributionCount = getAttributedOpsCount();
for (int i = attributionCount - 1; i >= 0; i--) {
final AttributedHistoricalOps attributionOps = getAttributedOpsAt(i);
@@ -5548,7 +5650,8 @@ public class AppOpsManager {
attributionOps.getTag())) {
mAttributedHistoricalOps.removeAt(i);
} else {
attributionOps.filter(opNames, filter, fractionToRemove);
attributionOps.filter(opNames, filter, historyFilter, fractionToRemove,
beginTimeMillis, endTimeMillis);
if (attributionOps.getOpCount() == 0) {
mAttributedHistoricalOps.removeAt(i);
}
@@ -5593,6 +5696,13 @@ public class AppOpsManager {
opCode, uidState, flags, increment);
}
private void addDiscreteAccess(int opCode, @Nullable String attributionTag,
@UidState int uidState, @OpFlags int flag, long discreteAccessTime,
long discreteAccessDuration) {
getOrCreateAttributedHistoricalOps(attributionTag).addDiscreteAccess(opCode, uidState,
flag, discreteAccessTime, discreteAccessDuration);
}
/**
* Gets the package name which the data represents.
*
@@ -5870,7 +5980,8 @@ public class AppOpsManager {
}
private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
double scaleFactor) {
@OpHistoryFlags int historyFilter, double scaleFactor, long beginTimeMillis,
long endTimeMillis) {
final int opCount = getOpCount();
for (int i = opCount - 1; i >= 0; i--) {
final HistoricalOp op = mHistoricalOps.valueAt(i);
@@ -5878,7 +5989,7 @@ public class AppOpsManager {
op.getOpName())) {
mHistoricalOps.removeAt(i);
} else {
op.filter(scaleFactor);
op.filter(historyFilter, scaleFactor, beginTimeMillis, endTimeMillis);
}
}
}
@@ -5909,6 +6020,12 @@ public class AppOpsManager {
getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment);
}
private void addDiscreteAccess(int opCode, @UidState int uidState, @OpFlags int flag,
long discreteAccessTime, long discreteAccessDuration) {
getOrCreateHistoricalOp(opCode).addDiscreteAccess(uidState,flag, discreteAccessTime,
discreteAccessDuration);
}
/**
* Gets number historical app ops.
*
@@ -5970,8 +6087,6 @@ public class AppOpsManager {
return op;
}
// Code below generated by codegen v1.0.14.
//
// DO NOT MODIFY!
@@ -6121,6 +6236,9 @@ public class AppOpsManager {
private @Nullable LongSparseLongArray mRejectCount;
private @Nullable LongSparseLongArray mAccessDuration;
/** Discrete Ops for this Op */
private @Nullable List<AttributedOpEntry> mDiscreteAccesses;
/** @hide */
public HistoricalOp(int op) {
mOp = op;
@@ -6137,6 +6255,12 @@ public class AppOpsManager {
if (other.mAccessDuration != null) {
mAccessDuration = other.mAccessDuration.clone();
}
final int historicalOpCount = other.getDiscreteAccessCount();
for (int i = 0; i < historicalOpCount; i++) {
final AttributedOpEntry origOp = other.getDiscreteAccessAt(i);
final AttributedOpEntry cloneOp = new AttributedOpEntry(origOp);
getOrCreateDiscreteAccesses().add(cloneOp);
}
}
private HistoricalOp(@NonNull Parcel parcel) {
@@ -6144,22 +6268,45 @@ public class AppOpsManager {
mAccessCount = readLongSparseLongArrayFromParcel(parcel);
mRejectCount = readLongSparseLongArrayFromParcel(parcel);
mAccessDuration = readLongSparseLongArrayFromParcel(parcel);
mDiscreteAccesses = readDiscreteAccessArrayFromParcel(parcel);
}
private void filter(double scaleFactor) {
scale(mAccessCount, scaleFactor);
scale(mRejectCount, scaleFactor);
scale(mAccessDuration, scaleFactor);
private void filter(@OpHistoryFlags int historyFlag, double scaleFactor,
long beginTimeMillis, long endTimeMillis) {
if ((historyFlag & HISTORY_FLAG_AGGREGATE) == 0) {
mAccessCount = null;
mRejectCount = null;
mAccessDuration = null;
} else {
scale(mAccessCount, scaleFactor);
scale(mRejectCount, scaleFactor);
scale(mAccessDuration, scaleFactor);
}
if ((historyFlag & HISTORY_FLAG_DISCRETE) == 0) {
mDiscreteAccesses = null;
return;
}
final int discreteOpCount = getDiscreteAccessCount();
for (int i = discreteOpCount - 1; i >= 0; i--) {
final AttributedOpEntry op = mDiscreteAccesses.get(i);
long opBeginTime = op.getLastAccessTime(OP_FLAGS_ALL);
long opEndTime = opBeginTime + op.getLastDuration(OP_FLAGS_ALL);
opEndTime = max(opBeginTime, opEndTime);
if (opEndTime < beginTimeMillis || opBeginTime > endTimeMillis) {
mDiscreteAccesses.remove(i);
}
}
}
private boolean isEmpty() {
return !hasData(mAccessCount)
&& !hasData(mRejectCount)
&& !hasData(mAccessDuration);
&& !hasData(mAccessDuration)
&& (mDiscreteAccesses == null);
}
private boolean hasData(@NonNull LongSparseLongArray array) {
return (array != null && array.size() > 0);
return array != null && array.size() > 0;
}
private @Nullable HistoricalOp splice(double fractionToRemove) {
@@ -6191,6 +6338,32 @@ public class AppOpsManager {
merge(this::getOrCreateAccessCount, other.mAccessCount);
merge(this::getOrCreateRejectCount, other.mRejectCount);
merge(this::getOrCreateAccessDuration, other.mAccessDuration);
if (other.mDiscreteAccesses == null) {
return;
}
if (mDiscreteAccesses == null) {
mDiscreteAccesses = new ArrayList(other.mDiscreteAccesses);
return;
}
List<AttributedOpEntry> historicalDiscreteAccesses = new ArrayList<>();
final int otherHistoricalOpCount = other.getDiscreteAccessCount();
final int historicalOpCount = getDiscreteAccessCount();
int i = 0;
int j = 0;
while (i < otherHistoricalOpCount || j < historicalOpCount) {
if (i == otherHistoricalOpCount) {
historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++));
} else if (j == historicalOpCount) {
historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
} else if (mDiscreteAccesses.get(j).getLastAccessTime(OP_FLAGS_ALL)
< other.mDiscreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL)) {
historicalDiscreteAccesses.add(mDiscreteAccesses.get(j++));
} else {
historicalDiscreteAccesses.add(other.mDiscreteAccesses.get(i++));
}
}
mDiscreteAccesses = historicalDiscreteAccesses;
}
private void increaseAccessCount(@UidState int uidState, @OpFlags int flags,
@@ -6218,6 +6391,23 @@ public class AppOpsManager {
}
}
private void addDiscreteAccess(@UidState int uidState, @OpFlags int flag,
long discreteAccessTime, long discreteAccessDuration) {
List<AttributedOpEntry> discreteAccesses = getOrCreateDiscreteAccesses();
LongSparseArray<NoteOpEvent> accessEvents = new LongSparseArray<>();
long key = makeKey(uidState, flag);
NoteOpEvent note = new NoteOpEvent(discreteAccessTime, discreteAccessDuration, null);
accessEvents.append(key, note);
AttributedOpEntry access = new AttributedOpEntry(mOp, false, accessEvents, null);
for (int i = discreteAccesses.size() - 1; i >= 0; i--) {
if (discreteAccesses.get(i).getLastAccessTime(OP_FLAGS_ALL) < discreteAccessTime) {
discreteAccesses.add(i + 1, access);
return;
}
}
discreteAccesses.add(0, access);
}
/**
* Gets the op name.
*
@@ -6232,6 +6422,33 @@ public class AppOpsManager {
return mOp;
}
/**
* Gets number of discrete historical app ops.
*
* @return The number historical app ops.
* @see #getOpAt(int)
*/
public @IntRange(from = 0) int getDiscreteAccessCount() {
if (mDiscreteAccesses == null) {
return 0;
}
return mDiscreteAccesses.size();
}
/**
* Gets the historical op at a given index.
*
* @param index The index to lookup.
* @return The op at the given index.
* @see #getOpCount()
*/
public @NonNull AttributedOpEntry getDiscreteAccessAt(@IntRange(from = 0) int index) {
if (mDiscreteAccesses == null) {
throw new IndexOutOfBoundsException();
}
return mDiscreteAccesses.get(index);
}
/**
* Gets the number times the op was accessed (performed) in the foreground.
*
@@ -6250,6 +6467,25 @@ public class AppOpsManager {
resolveFirstUnrestrictedUidState(mOp), flags);
}
/**
* Gets the discrete events the op was accessed (performed) in the foreground.
*
* @param flags The flags which are any combination of
* {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
* {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
* {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
* for any flag.
* @return The list of discrete ops accessed in the foreground.
*
* @see #getBackgroundDiscreteAccesses(int)
* @see #getDiscreteAccesses(int, int, int)
*/
@NonNull
public List<AttributedOpEntry> getForegroundDiscreteAccesses(@OpFlags int flags) {
return listForFlagsInStates(mDiscreteAccesses, MAX_PRIORITY_UID_STATE,
resolveFirstUnrestrictedUidState(mOp), flags);
}
/**
* Gets the number times the op was accessed (performed) in the background.
*
@@ -6268,6 +6504,25 @@ public class AppOpsManager {
MIN_PRIORITY_UID_STATE, flags);
}
/**
* Gets the discrete events the op was accessed (performed) in the background.
*
* @param flags The flags which are any combination of
* {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
* {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
* {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
* for any flag.
* @return The list of discrete ops accessed in the background.
*
* @see #getForegroundDiscreteAccesses(int)
* @see #getDiscreteAccesses(int, int, int)
*/
@NonNull
public List<AttributedOpEntry> getBackgroundDiscreteAccesses(@OpFlags int flags) {
return listForFlagsInStates(mDiscreteAccesses, resolveLastRestrictedUidState(mOp),
MIN_PRIORITY_UID_STATE, flags);
}
/**
* Gets the number times the op was accessed (performed) for a
* range of uid states.
@@ -6293,6 +6548,26 @@ public class AppOpsManager {
return sumForFlagsInStates(mAccessCount, fromUidState, toUidState, flags);
}
/**
* Gets the discrete events the op was accessed (performed) for a
* range of uid states.
*
* @param flags The flags which are any combination of
* {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
* {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
* {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
* for any flag.
* @return The discrete the op was accessed in the background.
*
* @see #getBackgroundDiscreteAccesses(int)
* @see #getForegroundDiscreteAccesses(int)
*/
@NonNull
public List<AttributedOpEntry> getDiscreteAccesses(@UidState int fromUidState,
@UidState int toUidState, @OpFlags int flags) {
return listForFlagsInStates(mDiscreteAccesses, fromUidState, toUidState, flags);
}
/**
* Gets the number times the op was rejected in the foreground.
*
@@ -6427,6 +6702,7 @@ public class AppOpsManager {
writeLongSparseLongArrayToParcel(mAccessCount, parcel);
writeLongSparseLongArrayToParcel(mRejectCount, parcel);
writeLongSparseLongArrayToParcel(mAccessDuration, parcel);
writeDiscreteAccessArrayToParcel(mDiscreteAccesses, parcel);
}
@Override
@@ -6447,7 +6723,11 @@ public class AppOpsManager {
if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) {
return false;
}
return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration);
if (!equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration)) {
return false;
}
return mDiscreteAccesses == null ? (other.mDiscreteAccesses == null ? true
: false) : mDiscreteAccesses.equals(other.mDiscreteAccesses);
}
@Override
@@ -6456,6 +6736,7 @@ public class AppOpsManager {
result = 31 * result + Objects.hashCode(mAccessCount);
result = 31 * result + Objects.hashCode(mRejectCount);
result = 31 * result + Objects.hashCode(mAccessDuration);
result = 31 * result + Objects.hashCode(mDiscreteAccesses);
return result;
}
@@ -6484,6 +6765,13 @@ public class AppOpsManager {
return mAccessDuration;
}
private @NonNull List<AttributedOpEntry> getOrCreateDiscreteAccesses() {
if (mDiscreteAccesses == null) {
mDiscreteAccesses = new ArrayList<>();
}
return mDiscreteAccesses;
}
/**
* Multiplies the entries in the array with the passed in scale factor and
* rounds the result at up 0.5 boundary.
@@ -6573,6 +6861,32 @@ public class AppOpsManager {
return sum;
}
/**
* Returns list of events filtered by UidState and UID flags.
*
* @param accesses The events list.
* @param beginUidState The beginning UID state (inclusive).
* @param endUidState The end UID state (inclusive).
* @param flags The UID flags.
* @return filtered list of events.
*/
private static List<AttributedOpEntry> listForFlagsInStates(List<AttributedOpEntry> accesses,
@UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
List<AttributedOpEntry> result = new ArrayList<>();
if (accesses == null) {
return result;
}
int nAccesses = accesses.size();
for (int i = 0; i < nAccesses; i++) {
AttributedOpEntry entry = accesses.get(i);
if (entry.getLastAccessTime(beginUidState, endUidState, flags) == -1) {
continue;
}
result.add(entry);
}
return result;
}
/**
* Callback for notification of changes to operation state.
*/
@@ -6796,8 +7110,9 @@ public class AppOpsManager {
Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.getHistoricalOps(request.mUid, request.mPackageName, request.mAttributionTag,
request.mOpNames, request.mFilter, request.mBeginTimeMillis,
request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
request.mOpNames, request.mHistoryFlags, request.mFilter,
request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -6835,9 +7150,9 @@ public class AppOpsManager {
Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
request.mAttributionTag, request.mOpNames, request.mFilter,
request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
new RemoteCallback((result) -> {
request.mAttributionTag, request.mOpNames, request.mHistoryFlags,
request.mFilter, request.mBeginTimeMillis, request.mEndTimeMillis,
request.mFlags, new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -9072,6 +9387,32 @@ public class AppOpsManager {
return array;
}
private static void writeDiscreteAccessArrayToParcel(
@Nullable List<AttributedOpEntry> array, @NonNull Parcel parcel) {
if (array != null) {
final int size = array.size();
parcel.writeInt(size);
for (int i = 0; i < size; i++) {
array.get(i).writeToParcel(parcel, 0);
}
} else {
parcel.writeInt(-1);
}
}
private static @Nullable List<AttributedOpEntry> readDiscreteAccessArrayFromParcel(
@NonNull Parcel parcel) {
final int size = parcel.readInt();
if (size < 0) {
return null;
}
final List<AttributedOpEntry> array = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
array.add(new AttributedOpEntry(parcel));
}
return array;
}
/**
* Collects the keys from an array to the result creating the result if needed.
*

View File

@@ -74,11 +74,11 @@ interface IAppOpsService {
@UnsupportedAppUsage
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
void getHistoricalOps(int uid, String packageName, String attributionTag, in List<String> ops,
int filter, long beginTimeMillis, long endTimeMillis, int flags,
int historyFlags, int filter, long beginTimeMillis, long endTimeMillis, int flags,
in RemoteCallback callback);
void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags,
in RemoteCallback callback);
in List<String> ops, int historyFlags, int filter, long beginTimeMillis,
long endTimeMillis, int flags, in RemoteCallback callback);
void offsetHistory(long duration);
void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
void addHistoricalOps(in AppOpsManager.HistoricalOps ops);

View File

@@ -843,11 +843,15 @@ public class AppOpsService extends IAppOpsService.Stub {
public void accessed(int proxyUid, @Nullable String proxyPackageName,
@Nullable String proxyAttributionTag, @AppOpsManager.UidState int uidState,
@OpFlags int flags) {
accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
long accessTime = System.currentTimeMillis();
accessed(accessTime, -1, proxyUid, proxyPackageName,
proxyAttributionTag, uidState, flags);
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
tag, uidState, flags);
mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid,
parent.packageName, parent.op, tag, flags, uidState, accessTime, -1);
}
/**
@@ -1004,8 +1008,10 @@ public class AppOpsService extends IAppOpsService.Stub {
OpEventProxyInfo proxyCopy = event.getProxy() != null
? new OpEventProxyInfo(event.getProxy()) : null;
long accessDurationMillis =
SystemClock.elapsedRealtime() - event.getStartElapsedTime();
NoteOpEvent finishedEvent = new NoteOpEvent(event.getStartTime(),
SystemClock.elapsedRealtime() - event.getStartElapsedTime(), proxyCopy);
accessDurationMillis, proxyCopy);
mAccessEvents.put(makeKey(event.getUidState(), event.getFlags()),
finishedEvent);
@@ -1013,6 +1019,10 @@ public class AppOpsService extends IAppOpsService.Stub {
parent.packageName, tag, event.getUidState(),
event.getFlags(), finishedEvent.getDuration());
mHistoricalRegistry.mDiscreteRegistry.recordDiscreteAccess(parent.uid,
parent.packageName, parent.op, tag, event.getFlags(), event.getUidState(),
event.getStartTime(), accessDurationMillis);
mInProgressStartOpEventPool.release(event);
if (mInProgressEvents.isEmpty()) {
@@ -2087,8 +2097,8 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void getHistoricalOps(int uid, String packageName, String attributionTag,
List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
int flags, RemoteCallback callback) {
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
PackageManager pm = mContext.getPackageManager();
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
@@ -2120,14 +2130,14 @@ public class AppOpsService extends IAppOpsService.Stub {
// Must not hold the appops lock
mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, filter,
beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String attributionTag,
List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
int flags, RemoteCallback callback) {
List<String> opNames, int dataType, int filter, long beginTimeMillis,
long endTimeMillis, int flags, RemoteCallback callback) {
ensureHistoricalOpRequestIsValid(uid, packageName, attributionTag, opNames, filter,
beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
@@ -2140,7 +2150,7 @@ public class AppOpsService extends IAppOpsService.Stub {
// Must not hold the appops lock
mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray,
mHistoricalRegistry, uid, packageName, attributionTag, opNamesArray, dataType,
filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@@ -4759,6 +4769,7 @@ public class AppOpsService extends IAppOpsService.Stub {
mFile.failWrite(stream);
}
}
mHistoricalRegistry.mDiscreteRegistry.writeAndClearAccessHistory();
}
static class Shell extends ShellCommand {
@@ -6115,6 +6126,7 @@ public class AppOpsService extends IAppOpsService.Stub {
"clearHistory");
// Must not hold the appops lock
mHistoricalRegistry.clearHistory();
mHistoricalRegistry.mDiscreteRegistry.clearHistory();
}
@Override

View File

@@ -0,0 +1,616 @@
/*
* Copyright (C) 2021 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.server.appop;
import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static java.lang.Math.max;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Process;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* This class manages information about recent accesses to ops for
* permission usage timeline.
*
* The timeline history is kept for limited time (initial default is 24 hours) and
* discarded after that.
*
* Every time state is saved (default is 30 minutes), memory state is dumped to a
* new file and memory state is cleared. Files older than time limit are deleted
* during the process.
*
* When request comes in, files are read and requested information is collected
* and delivered.
*/
final class DiscreteRegistry {
static final String TIMELINE_FILE_SUFFIX = "tl";
private static final String TAG = DiscreteRegistry.class.getSimpleName();
private static final long TIMELINE_HISTORY_CUTOFF = Duration.ofHours(24).toMillis();
private static final String TAG_HISTORY = "h";
private static final String ATTR_VERSION = "v";
private static final int CURRENT_VERSION = 1;
private static final String TAG_UID = "u";
private static final String ATTR_UID = "ui";
private static final String TAG_PACKAGE = "p";
private static final String ATTR_PACKAGE_NAME = "pn";
private static final String TAG_OP = "o";
private static final String ATTR_OP_ID = "op";
private static final String TAG_TAG = "a";
private static final String ATTR_TAG = "at";
private static final String TAG_ENTRY = "e";
private static final String ATTR_NOTE_TIME = "nt";
private static final String ATTR_NOTE_DURATION = "nd";
private static final String ATTR_UID_STATE = "us";
private static final String ATTR_FLAGS = "f";
// Lock for read/write access to on disk state
private final Object mOnDiskLock = new Object();
//Lock for read/write access to in memory state
private final @NonNull Object mInMemoryLock;
@GuardedBy("mOnDiskLock")
private final File mDiscreteAccessDir;
@GuardedBy("mInMemoryLock")
private DiscreteOps mDiscreteOps;
DiscreteRegistry(Object inMemoryLock) {
mInMemoryLock = inMemoryLock;
mDiscreteAccessDir = new File(new File(Environment.getDataSystemDirectory(), "appops"),
"discrete");
createDiscreteAccessDir();
mDiscreteOps = new DiscreteOps();
}
private void createDiscreteAccessDir() {
if (!mDiscreteAccessDir.exists()) {
if (!mDiscreteAccessDir.mkdirs()) {
Slog.e(TAG, "Failed to create DiscreteRegistry directory");
}
FileUtils.setPermissions(mDiscreteAccessDir.getPath(),
FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IXOTH, -1, -1);
}
}
void recordDiscreteAccess(int uid, String packageName, int op, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime,
long accessDuration) {
if (!isDiscreteOp(op, uid, flags)) {
return;
}
synchronized (mInMemoryLock) {
mDiscreteOps.addDiscreteAccess(op, uid, packageName, attributionTag, flags, uidState,
accessTime, accessDuration);
}
}
void writeAndClearAccessHistory() {
synchronized (mOnDiskLock) {
final File[] files = mDiscreteAccessDir.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
final String fileName = f.getName();
if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
continue;
}
try {
long timestamp = Long.valueOf(fileName.substring(0,
fileName.length() - TIMELINE_FILE_SUFFIX.length()));
if (Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
ChronoUnit.MILLIS).toEpochMilli() > timestamp) {
f.delete();
Slog.e(TAG, "Deleting file " + fileName);
}
} catch (Throwable t) {
Slog.e(TAG, "Error while cleaning timeline files: " + t.getMessage() + " "
+ t.getStackTrace());
}
}
}
}
DiscreteOps discreteOps;
synchronized (mInMemoryLock) {
discreteOps = mDiscreteOps;
mDiscreteOps = new DiscreteOps();
}
if (discreteOps.isEmpty()) {
return;
}
long currentTimeStamp = Instant.now().toEpochMilli();
try {
final File file = new File(mDiscreteAccessDir, currentTimeStamp + TIMELINE_FILE_SUFFIX);
discreteOps.writeToFile(file);
} catch (Throwable t) {
Slog.e(TAG,
"Error writing timeline state: " + t.getMessage() + " "
+ Arrays.toString(t.getStackTrace()));
}
}
void getHistoricalDiscreteOps(AppOpsManager.HistoricalOps result, long beginTimeMillis,
long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
@Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
@Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
writeAndClearAccessHistory();
DiscreteOps discreteOps = new DiscreteOps();
readDiscreteOpsFromDisk(discreteOps, beginTimeMillis, endTimeMillis, filter, uidFilter,
packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
discreteOps.applyToHistoricalOps(result);
return;
}
private void readDiscreteOpsFromDisk(DiscreteOps discreteOps, long beginTimeMillis,
long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
@Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
@Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
synchronized (mOnDiskLock) {
long historyBeginTimeMillis = Instant.now().minus(TIMELINE_HISTORY_CUTOFF,
ChronoUnit.MILLIS).toEpochMilli();
if (historyBeginTimeMillis > endTimeMillis) {
return;
}
beginTimeMillis = max(beginTimeMillis, historyBeginTimeMillis);
final File[] files = mDiscreteAccessDir.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
final String fileName = f.getName();
if (!fileName.endsWith(TIMELINE_FILE_SUFFIX)) {
continue;
}
long timestamp = Long.valueOf(fileName.substring(0,
fileName.length() - TIMELINE_FILE_SUFFIX.length()));
if (timestamp < beginTimeMillis) {
continue;
}
discreteOps.readFromFile(f, beginTimeMillis, endTimeMillis, filter, uidFilter,
packageNameFilter, opNamesFilter, attributionTagFilter, flagsFilter);
}
}
}
}
void clearHistory() {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
mDiscreteOps = new DiscreteOps();
}
FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
createDiscreteAccessDir();
}
}
public static boolean isDiscreteOp(int op, int uid, @AppOpsManager.OpFlags int flags) {
if (!isDiscreteOp(op)) {
return false;
}
if (!isDiscreteUid(uid)) {
return false;
}
if ((flags & (OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED)) == 0) {
return false;
}
return true;
}
static boolean isDiscreteOp(int op) {
if (op != OP_CAMERA && op != OP_RECORD_AUDIO && op != OP_FINE_LOCATION
&& op != OP_COARSE_LOCATION) {
return false;
}
return true;
}
static boolean isDiscreteUid(int uid) {
if (uid < Process.FIRST_APPLICATION_UID) {
return false;
}
return true;
}
private final class DiscreteOps {
ArrayMap<Integer, DiscreteUidOps> mUids;
DiscreteOps() {
mUids = new ArrayMap<>();
}
void addDiscreteAccess(int op, int uid, @NonNull String packageName,
@Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
@AppOpsManager.UidState int uidState, long accessTime, long accessDuration) {
getOrCreateDiscreteUidOps(uid).addDiscreteAccess(op, packageName, attributionTag, flags,
uidState, accessTime, accessDuration);
}
private void applyToHistoricalOps(AppOpsManager.HistoricalOps result) {
int nUids = mUids.size();
for (int i = 0; i < nUids; i++) {
mUids.valueAt(i).applyToHistory(result, mUids.keyAt(i));
}
}
private void writeToFile(File f) throws Exception {
FileOutputStream stream = new FileOutputStream(f);
TypedXmlSerializer out = Xml.resolveSerializer(stream);
out.startDocument(null, true);
out.startTag(null, TAG_HISTORY);
out.attributeInt(null, ATTR_VERSION, CURRENT_VERSION);
int nUids = mUids.size();
for (int i = 0; i < nUids; i++) {
out.startTag(null, TAG_UID);
out.attributeInt(null, ATTR_UID, mUids.keyAt(i));
mUids.valueAt(i).serialize(out);
out.endTag(null, TAG_UID);
}
out.endTag(null, TAG_HISTORY);
out.endDocument();
stream.close();
}
private DiscreteUidOps getOrCreateDiscreteUidOps(int uid) {
DiscreteUidOps result = mUids.get(uid);
if (result == null) {
result = new DiscreteUidOps();
mUids.put(uid, result);
}
return result;
}
boolean isEmpty() {
return mUids.isEmpty();
}
private void readFromFile(File f, long beginTimeMillis, long endTimeMillis,
@AppOpsManager.HistoricalOpsRequestFilter int filter,
int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
@Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter) {
try {
FileInputStream stream = new FileInputStream(f);
TypedXmlPullParser parser = Xml.resolvePullParser(stream);
XmlUtils.beginDocument(parser, TAG_HISTORY);
// We haven't released version 1 and have more detailed
// accounting - just nuke the current state
final int version = parser.getAttributeInt(null, ATTR_VERSION);
if (version != CURRENT_VERSION) {
throw new IllegalStateException("Dropping unsupported discrete history " + f);
}
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_UID.equals(parser.getName())) {
int uid = parser.getAttributeInt(null, ATTR_UID, -1);
if ((filter & FILTER_BY_UID) != 0 && uid != uidFilter) {
continue;
}
getOrCreateDiscreteUidOps(uid).deserialize(parser, beginTimeMillis,
endTimeMillis, filter, packageNameFilter, opNamesFilter,
attributionTagFilter, flagsFilter);
}
}
} catch (Throwable t) {
Slog.e(TAG, "Failed to read file " + f.getName() + " " + t.getMessage() + " "
+ Arrays.toString(t.getStackTrace()));
}
}
}
private final class DiscreteUidOps {
ArrayMap<String, DiscretePackageOps> mPackages;
DiscreteUidOps() {
mPackages = new ArrayMap<>();
}
void addDiscreteAccess(int op, @NonNull String packageName, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
getOrCreateDiscretePackageOps(packageName).addDiscreteAccess(op, attributionTag, flags,
uidState, accessTime, accessDuration);
}
private DiscretePackageOps getOrCreateDiscretePackageOps(String packageName) {
DiscretePackageOps result = mPackages.get(packageName);
if (result == null) {
result = new DiscretePackageOps();
mPackages.put(packageName, result);
}
return result;
}
private void applyToHistory(AppOpsManager.HistoricalOps result, int uid) {
int nPackages = mPackages.size();
for (int i = 0; i < nPackages; i++) {
mPackages.valueAt(i).applyToHistory(result, uid, mPackages.keyAt(i));
}
}
void serialize(TypedXmlSerializer out) throws Exception {
int nPackages = mPackages.size();
for (int i = 0; i < nPackages; i++) {
out.startTag(null, TAG_PACKAGE);
out.attribute(null, ATTR_PACKAGE_NAME, mPackages.keyAt(i));
mPackages.valueAt(i).serialize(out);
out.endTag(null, TAG_PACKAGE);
}
}
void deserialize(TypedXmlPullParser parser, long beginTimeMillis,
long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter,
@Nullable String packageNameFilter,
@Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
@AppOpsManager.OpFlags int flagsFilter) throws Exception {
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
if ((filter & FILTER_BY_PACKAGE_NAME) != 0
&& !packageName.equals(packageNameFilter)) {
continue;
}
getOrCreateDiscretePackageOps(packageName).deserialize(parser, beginTimeMillis,
endTimeMillis, filter, opNamesFilter, attributionTagFilter,
flagsFilter);
}
}
}
}
private final class DiscretePackageOps {
ArrayMap<Integer, DiscreteOp> mPackageOps;
DiscretePackageOps() {
mPackageOps = new ArrayMap<>();
}
void addDiscreteAccess(int op, @Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
getOrCreateDiscreteOp(op).addDiscreteAccess(attributionTag, flags, uidState, accessTime,
accessDuration);
}
private DiscreteOp getOrCreateDiscreteOp(int op) {
DiscreteOp result = mPackageOps.get(op);
if (result == null) {
result = new DiscreteOp();
mPackageOps.put(op, result);
}
return result;
}
private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
@NonNull String packageName) {
int nPackageOps = mPackageOps.size();
for (int i = 0; i < nPackageOps; i++) {
mPackageOps.valueAt(i).applyToHistory(result, uid, packageName,
mPackageOps.keyAt(i));
}
}
void serialize(TypedXmlSerializer out) throws Exception {
int nOps = mPackageOps.size();
for (int i = 0; i < nOps; i++) {
out.startTag(null, TAG_OP);
out.attributeInt(null, ATTR_OP_ID, mPackageOps.keyAt(i));
mPackageOps.valueAt(i).serialize(out);
out.endTag(null, TAG_OP);
}
}
void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
@AppOpsManager.HistoricalOpsRequestFilter int filter,
@Nullable String[] opNamesFilter, @Nullable String attributionTagFilter,
@AppOpsManager.OpFlags int flagsFilter) throws Exception {
int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OP.equals(parser.getName())) {
int op = parser.getAttributeInt(null, ATTR_OP_ID);
if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNamesFilter,
AppOpsManager.opToPublicName(op))) {
continue;
}
getOrCreateDiscreteOp(op).deserialize(parser, beginTimeMillis, endTimeMillis,
filter, attributionTagFilter, flagsFilter);
}
}
}
}
private final class DiscreteOp {
ArrayMap<String, List<DiscreteOpEvent>> mAttributedOps;
DiscreteOp() {
mAttributedOps = new ArrayMap<>();
}
void addDiscreteAccess(@Nullable String attributionTag,
@AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState,
long accessTime, long accessDuration) {
List<DiscreteOpEvent> attributedOps = getOrCreateDiscreteOpEventsList(
attributionTag);
accessTime = Instant.ofEpochMilli(accessTime).truncatedTo(
ChronoUnit.MINUTES).toEpochMilli();
int nAttributedOps = attributedOps.size();
for (int i = nAttributedOps - 1; i >= 0; i--) {
DiscreteOpEvent previousOp = attributedOps.get(i);
if (previousOp.mNoteTime < accessTime) {
break;
}
if (previousOp.mOpFlag == flags && previousOp.mUidState == uidState) {
return;
}
}
attributedOps.add(new DiscreteOpEvent(accessTime, accessDuration, uidState, flags));
}
private List<DiscreteOpEvent> getOrCreateDiscreteOpEventsList(String attributionTag) {
List<DiscreteOpEvent> result = mAttributedOps.get(attributionTag);
if (result == null) {
result = new ArrayList<>();
mAttributedOps.put(attributionTag, result);
}
return result;
}
private void applyToHistory(AppOpsManager.HistoricalOps result, int uid,
@NonNull String packageName, int op) {
int nOps = mAttributedOps.size();
for (int i = 0; i < nOps; i++) {
String tag = mAttributedOps.keyAt(i);
List<DiscreteOpEvent> events = mAttributedOps.valueAt(i);
int nEvents = events.size();
for (int j = 0; j < nEvents; j++) {
DiscreteOpEvent event = events.get(j);
result.addDiscreteAccess(op, uid, packageName, tag, event.mUidState,
event.mOpFlag, event.mNoteTime, event.mNoteDuration);
}
}
}
void serialize(TypedXmlSerializer out) throws Exception {
int nAttributions = mAttributedOps.size();
for (int i = 0; i < nAttributions; i++) {
out.startTag(null, TAG_TAG);
String tag = mAttributedOps.keyAt(i);
if (tag != null) {
out.attribute(null, ATTR_TAG, mAttributedOps.keyAt(i));
}
List<DiscreteOpEvent> ops = mAttributedOps.valueAt(i);
int nOps = ops.size();
for (int j = 0; j < nOps; j++) {
out.startTag(null, TAG_ENTRY);
ops.get(j).serialize(out);
out.endTag(null, TAG_ENTRY);
}
out.endTag(null, TAG_TAG);
}
}
void deserialize(TypedXmlPullParser parser, long beginTimeMillis, long endTimeMillis,
@AppOpsManager.HistoricalOpsRequestFilter int filter,
@Nullable String attributionTagFilter,
@AppOpsManager.OpFlags int flagsFilter) throws Exception {
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_TAG.equals(parser.getName())) {
String attributionTag = parser.getAttributeValue(null, ATTR_TAG);
if ((filter & FILTER_BY_ATTRIBUTION_TAG) != 0 && !attributionTag.equals(
attributionTagFilter)) {
continue;
}
List<DiscreteOpEvent> events = getOrCreateDiscreteOpEventsList(
attributionTag);
int innerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, innerDepth)) {
if (TAG_ENTRY.equals(parser.getName())) {
long noteTime = parser.getAttributeLong(null, ATTR_NOTE_TIME);
long noteDuration = parser.getAttributeLong(null, ATTR_NOTE_DURATION,
-1);
int uidState = parser.getAttributeInt(null, ATTR_UID_STATE);
int opFlags = parser.getAttributeInt(null, ATTR_FLAGS);
if ((flagsFilter & opFlags) == 0) {
continue;
}
if ((noteTime + noteDuration < beginTimeMillis
&& noteTime > endTimeMillis)) {
continue;
}
DiscreteOpEvent event = new DiscreteOpEvent(noteTime, noteDuration,
uidState, opFlags);
events.add(event);
}
}
Collections.sort(events, (a, b) -> a.mNoteTime < b.mNoteTime ? -1
: (a.mNoteTime == b.mNoteTime ? 0 : 1));
}
}
}
}
private final class DiscreteOpEvent {
final long mNoteTime;
final long mNoteDuration;
final @AppOpsManager.UidState int mUidState;
final @AppOpsManager.OpFlags int mOpFlag;
DiscreteOpEvent(long noteTime, long noteDuration, @AppOpsManager.UidState int uidState,
@AppOpsManager.OpFlags int opFlag) {
mNoteTime = noteTime;
mNoteDuration = noteDuration;
mUidState = uidState;
mOpFlag = opFlag;
}
private void serialize(TypedXmlSerializer out) throws Exception {
out.attributeLong(null, ATTR_NOTE_TIME, mNoteTime);
if (mNoteDuration != -1) {
out.attributeLong(null, ATTR_NOTE_DURATION, mNoteDuration);
}
out.attributeInt(null, ATTR_UID_STATE, mUidState);
out.attributeInt(null, ATTR_FLAGS, mOpFlag);
}
}
}

View File

@@ -19,6 +19,8 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.HISTORY_FLAG_AGGREGATE;
import static android.app.AppOpsManager.HISTORY_FLAG_DISCRETE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -30,6 +32,7 @@ import android.app.AppOpsManager.HistoricalOpsRequestFilter;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.HistoricalUidOps;
import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManager.OpHistoryFlags;
import android.app.AppOpsManager.UidState;
import android.content.ContentResolver;
import android.database.ContentObserver;
@@ -61,9 +64,7 @@ import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
@@ -71,7 +72,6 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -85,7 +85,7 @@ import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* This class managers historical app op state. This includes reading, persistence,
* This class manages historical app op state. This includes reading, persistence,
* accounting, querying.
* <p>
* The history is kept forever in multiple files. Each file time contains the
@@ -138,6 +138,8 @@ final class HistoricalRegistry {
private static final String PARAMETER_ASSIGNMENT = "=";
private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
volatile @NonNull DiscreteRegistry mDiscreteRegistry;
@GuardedBy("mLock")
private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
@@ -199,6 +201,7 @@ final class HistoricalRegistry {
HistoricalRegistry(@NonNull Object lock) {
mInMemoryLock = lock;
mDiscreteRegistry = new DiscreteRegistry(lock);
}
HistoricalRegistry(@NonNull HistoricalRegistry other) {
@@ -352,36 +355,49 @@ final class HistoricalRegistry {
}
}
void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
void getHistoricalOpsFromDiskRaw(int uid, @Nullable String packageName,
@Nullable String attributionTag, @Nullable String[] opNames,
@HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
@OpFlags int flags, @NonNull RemoteCallback callback) {
@OpHistoryFlags int historyFlags, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
@NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
return;
}
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
callback.sendResult(new Bundle());
return;
final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
synchronized (mOnDiskLock) {
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
callback.sendResult(new Bundle());
return;
}
mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
attributionTag,
opNames, filter, beginTimeMillis, endTimeMillis, flags);
}
final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
opNames, filter, beginTimeMillis, endTimeMillis, flags);
final Bundle payload = new Bundle();
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
}
}
if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
filter, uid, packageName, opNames, attributionTag,
flags);
}
final Bundle payload = new Bundle();
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
}
void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String attributionTag,
@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
@NonNull RemoteCallback callback) {
void getHistoricalOps(int uid, @Nullable String packageName, @Nullable String attributionTag,
@Nullable String[] opNames, @OpHistoryFlags int historyFlags,
@HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
@OpFlags int flags, @NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
return;
@@ -392,6 +408,8 @@ final class HistoricalRegistry {
endTimeMillis = currentTimeMillis;
}
final Bundle payload = new Bundle();
// Argument times are based off epoch start while our internal store is
// based off now, so take this into account.
final long inMemoryAdjBeginTimeMillis = Math.max(currentTimeMillis - endTimeMillis, 0);
@@ -399,55 +417,63 @@ final class HistoricalRegistry {
final HistoricalOps result = new HistoricalOps(inMemoryAdjBeginTimeMillis,
inMemoryAdjEndTimeMillis);
synchronized (mOnDiskLock) {
final List<HistoricalOps> pendingWrites;
final HistoricalOps currentOps;
boolean collectOpsFromDisk;
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
callback.sendResult(new Bundle());
return;
}
currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
|| inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
// Some of the current batch falls into the query, so extract that.
final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
currentOpsCopy.filter(uid, packageName, attributionTag, opNames, filter,
inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
result.merge(currentOpsCopy);
}
pendingWrites = new ArrayList<>(mPendingWrites);
mPendingWrites.clear();
collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
}
// If the query was only for in-memory state - done.
if (collectOpsFromDisk) {
// If there is a write in flight we need to force it now
persistPendingHistory(pendingWrites);
// Collect persisted state.
final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
- mNextPersistDueTimeMillis + mBaseSnapshotInterval;
final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, attributionTag,
opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
}
// Rebase the result time to be since epoch.
result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
// Send back the result.
final Bundle payload = new Bundle();
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
if ((historyFlags & HISTORY_FLAG_DISCRETE) != 0) {
mDiscreteRegistry.getHistoricalDiscreteOps(result, beginTimeMillis, endTimeMillis,
filter, uid, packageName, opNames, attributionTag, flags);
}
if ((historyFlags & HISTORY_FLAG_AGGREGATE) != 0) {
synchronized (mOnDiskLock) {
final List<HistoricalOps> pendingWrites;
final HistoricalOps currentOps;
boolean collectOpsFromDisk;
synchronized (mInMemoryLock) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
callback.sendResult(new Bundle());
return;
}
currentOps = getUpdatedPendingHistoricalOpsMLocked(currentTimeMillis);
if (!(inMemoryAdjBeginTimeMillis >= currentOps.getEndTimeMillis()
|| inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
// Some of the current batch falls into the query, so extract that.
final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
currentOpsCopy.filter(uid, packageName, attributionTag, opNames,
historyFlags, filter, inMemoryAdjBeginTimeMillis,
inMemoryAdjEndTimeMillis);
result.merge(currentOpsCopy);
}
pendingWrites = new ArrayList<>(mPendingWrites);
mPendingWrites.clear();
collectOpsFromDisk = inMemoryAdjEndTimeMillis > currentOps.getEndTimeMillis();
}
// If the query was only for in-memory state - done.
if (collectOpsFromDisk) {
// If there is a write in flight we need to force it now
persistPendingHistory(pendingWrites);
// Collect persisted state.
final long onDiskAndInMemoryOffsetMillis = currentTimeMillis
- mNextPersistDueTimeMillis + mBaseSnapshotInterval;
final long onDiskAdjBeginTimeMillis = Math.max(inMemoryAdjBeginTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
mPersistence.collectHistoricalOpsDLocked(result, uid, packageName,
attributionTag,
opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis,
flags);
}
}
}
// Rebase the result time to be since epoch.
result.setBeginAndEndTime(beginTimeMillis, endTimeMillis);
// Send back the result.
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
}
void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
@@ -692,6 +718,7 @@ final class HistoricalRegistry {
}
persistPendingHistory(pendingWrites);
}
mDiscreteRegistry.writeAndClearAccessHistory();
}
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {