Merge "Add API support for permission access timeline." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
4f5072825d
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user