diff options
Diffstat (limited to 'apex')
45 files changed, 2275 insertions, 449 deletions
diff --git a/apex/Android.bp b/apex/Android.bp index abebfa39fada..362cf95b3832 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -29,6 +29,16 @@ mainline_stubs_args = // TODO: remove this server classes are cleaned up. mainline_stubs_args += "--hide-package com.android.server " +priv_apps = " " + + "--show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + + "\\) " + +module_libs = " " + + " --show-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + + "\\) " + stubs_defaults { name: "framework-module-stubs-defaults-publicapi", args: mainline_stubs_args, @@ -37,36 +47,23 @@ stubs_defaults { stubs_defaults { name: "framework-module-stubs-defaults-systemapi", - args: mainline_stubs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\) ", + args: mainline_stubs_args + priv_apps, installable: false, } +// The defaults for module_libs comes in two parts - defaults for API checks +// and defaults for stub generation. This is because we want the API txt +// files to *only* include the module_libs_api, but the stubs to include +// module_libs_api as well as priv_apps. + stubs_defaults { - name: "framework-module-stubs-defaults-module_apps_api", - args: mainline_stubs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\) " + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\) ", + name: "framework-module-api-defaults-module_libs_api", + args: mainline_stubs_args + module_libs, installable: false, } stubs_defaults { name: "framework-module-stubs-defaults-module_libs_api", - args: mainline_stubs_args + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\) " + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_APPS," + - "process=android.annotation.SystemApi.Process.ALL\\) " + - " --show-annotation android.annotation.SystemApi\\(" + - "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," + - "process=android.annotation.SystemApi.Process.ALL\\) ", + args: mainline_stubs_args + module_libs + priv_apps, installable: false, } diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING index 4dc0c49380c8..25a15371ae47 100644 --- a/apex/blobstore/TEST_MAPPING +++ b/apex/blobstore/TEST_MAPPING @@ -2,6 +2,14 @@ "presubmit": [ { "name": "CtsBlobStoreTestCases" + }, + { + "name": "FrameworksMockingServicesTests", + "options": [ + { + "include-filter": "com.android.server.blob" + } + ] } ] }
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java index f7e6a987ded3..d339afac5c77 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobHandle.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobHandle.java @@ -45,6 +45,10 @@ import java.util.Objects; public final class BlobHandle implements Parcelable { private static final String ALGO_SHA_256 = "SHA-256"; + private static final String[] SUPPORTED_ALGOS = { + ALGO_SHA_256 + }; + private static final int LIMIT_BLOB_TAG_LENGTH = 128; // characters /** @@ -104,14 +108,9 @@ public final class BlobHandle implements Parcelable { public static @NonNull BlobHandle create(@NonNull String algorithm, @NonNull byte[] digest, @NonNull CharSequence label, @CurrentTimeMillisLong long expiryTimeMillis, @NonNull String tag) { - Preconditions.checkNotNull(algorithm, "algorithm must not be null"); - Preconditions.checkNotNull(digest, "digest must not be null"); - Preconditions.checkNotNull(label, "label must not be null"); - Preconditions.checkArgumentNonnegative(expiryTimeMillis, - "expiryTimeMillis must not be negative"); - Preconditions.checkNotNull(tag, "tag must not be null"); - Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long"); - return new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag); + final BlobHandle handle = new BlobHandle(algorithm, digest, label, expiryTimeMillis, tag); + handle.assertIsValid(); + return handle; } /** @@ -215,12 +214,52 @@ public final class BlobHandle implements Parcelable { } /** @hide */ - public void dump(IndentingPrintWriter fout) { - fout.println("algo: " + algorithm); - fout.println("digest: " + Base64.encodeToString(digest, Base64.NO_WRAP)); - fout.println("label: " + label); - fout.println("expiryMs: " + expiryTimeMillis); - fout.println("tag: " + tag); + public void dump(IndentingPrintWriter fout, boolean dumpFull) { + if (dumpFull) { + fout.println("algo: " + algorithm); + fout.println("digest: " + (dumpFull ? encodeDigest() : safeDigest())); + fout.println("label: " + label); + fout.println("expiryMs: " + expiryTimeMillis); + fout.println("tag: " + tag); + } else { + fout.println(toString()); + } + } + + /** @hide */ + public void assertIsValid() { + Preconditions.checkArgumentIsSupported(SUPPORTED_ALGOS, algorithm); + Preconditions.checkByteArrayNotEmpty(digest, "digest"); + Preconditions.checkStringNotEmpty(label, "label must not be null"); + Preconditions.checkArgumentNonnegative(expiryTimeMillis, + "expiryTimeMillis must not be negative"); + Preconditions.checkStringNotEmpty(tag, "tag must not be null"); + Preconditions.checkArgument(tag.length() <= LIMIT_BLOB_TAG_LENGTH, "tag too long"); + } + + @Override + public String toString() { + return "BlobHandle {" + + "algo:" + algorithm + "," + + "digest:" + safeDigest() + "," + + "label:" + label + "," + + "expiryMs:" + expiryTimeMillis + "," + + "tag:" + tag + + "}"; + } + + private String safeDigest() { + final String digestStr = encodeDigest(); + return digestStr.substring(0, 2) + ".." + digestStr.substring(digestStr.length() - 2); + } + + private String encodeDigest() { + return Base64.encodeToString(digest, Base64.NO_WRAP); + } + + /** @hide */ + public boolean isExpired() { + return expiryTimeMillis != 0 && expiryTimeMillis < System.currentTimeMillis(); } public static final @NonNull Creator<BlobHandle> CREATOR = new Creator<BlobHandle>() { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java index e9838d6b9712..c12e0ec8aec9 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -48,6 +48,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -63,9 +64,9 @@ class BlobMetadata { private final Context mContext; - public final long blobId; - public final BlobHandle blobHandle; - public final int userId; + private final long mBlobId; + private final BlobHandle mBlobHandle; + private final int mUserId; @GuardedBy("mMetadataLock") private final ArraySet<Committer> mCommitters = new ArraySet<>(); @@ -89,9 +90,21 @@ class BlobMetadata { BlobMetadata(Context context, long blobId, BlobHandle blobHandle, int userId) { mContext = context; - this.blobId = blobId; - this.blobHandle = blobHandle; - this.userId = userId; + this.mBlobId = blobId; + this.mBlobHandle = blobHandle; + this.mUserId = userId; + } + + long getBlobId() { + return mBlobId; + } + + BlobHandle getBlobHandle() { + return mBlobHandle; + } + + int getUserId() { + return mUserId; } void addCommitter(@NonNull Committer committer) { @@ -156,6 +169,12 @@ class BlobMetadata { } } + boolean hasLeases() { + synchronized (mMetadataLock) { + return !mLeasees.isEmpty(); + } + } + boolean isAccessAllowedForCaller(String callingPackage, int callingUid) { // TODO: verify blob is still valid (expiryTime is not elapsed) synchronized (mMetadataLock) { @@ -189,7 +208,7 @@ class BlobMetadata { File getBlobFile() { if (mBlobFile == null) { - mBlobFile = BlobStoreConfig.getBlobFile(blobId); + mBlobFile = BlobStoreConfig.getBlobFile(mBlobId); } return mBlobFile; } @@ -234,10 +253,10 @@ class BlobMetadata { return revocableFd.getRevocableFileDescriptor(); } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("Committers:"); @@ -267,11 +286,11 @@ class BlobMetadata { void writeToXml(XmlSerializer out) throws IOException { synchronized (mMetadataLock) { - XmlUtils.writeLongAttribute(out, ATTR_ID, blobId); - XmlUtils.writeIntAttribute(out, ATTR_USER_ID, userId); + XmlUtils.writeLongAttribute(out, ATTR_ID, mBlobId); + XmlUtils.writeIntAttribute(out, ATTR_USER_ID, mUserId); out.startTag(null, TAG_BLOB_HANDLE); - blobHandle.writeToXml(out); + mBlobHandle.writeToXml(out); out.endTag(null, TAG_BLOB_HANDLE); for (int i = 0, count = mCommitters.size(); i < count; ++i) { diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index eb414b0f11a6..ba2e559afdab 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -18,12 +18,15 @@ package com.android.server.blob; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Environment; +import android.util.Log; import android.util.Slog; import java.io.File; +import java.util.concurrent.TimeUnit; class BlobStoreConfig { public static final String TAG = "BlobStore"; + public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); public static final int CURRENT_XML_VERSION = 1; @@ -32,6 +35,20 @@ class BlobStoreConfig { private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml"; private static final String BLOBS_INDEX_FILE_NAME = "blobs_index.xml"; + /** + * Job Id for idle maintenance job ({@link BlobStoreIdleJobService}). + */ + public static final int IDLE_JOB_ID = 0xB70B1D7; // 191934935L + /** + * Max time period (in millis) between each idle maintenance job run. + */ + public static final long IDLE_JOB_PERIOD_MILLIS = TimeUnit.DAYS.toMillis(1); + + /** + * Timeout in millis after which sessions with no updates will be deleted. + */ + public static final long SESSION_EXPIRY_TIMEOUT_MILLIS = TimeUnit.DAYS.toMillis(7); + @Nullable public static File prepareBlobFile(long sessionId) { final File blobsDir = prepareBlobsDir(); diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java new file mode 100644 index 000000000000..460e776b9ff6 --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreIdleJobService.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.blob; + +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_ID; +import static com.android.server.blob.BlobStoreConfig.IDLE_JOB_PERIOD_MILLIS; +import static com.android.server.blob.BlobStoreConfig.LOGV; +import static com.android.server.blob.BlobStoreConfig.TAG; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; +import android.os.AsyncTask; +import android.util.Slog; + +import com.android.server.LocalServices; + +/** + * Maintenance job to clean up stale sessions and blobs. + */ +public class BlobStoreIdleJobService extends JobService { + @Override + public boolean onStartJob(final JobParameters params) { + AsyncTask.execute(() -> { + final BlobStoreManagerInternal blobStoreManagerInternal = LocalServices.getService( + BlobStoreManagerInternal.class); + blobStoreManagerInternal.onIdleMaintenance(); + jobFinished(params, false); + }); + return false; + } + + @Override + public boolean onStopJob(final JobParameters params) { + Slog.d(TAG, "Idle maintenance job is stopped; id=" + params.getJobId() + + ", reason=" + JobParameters.getReasonCodeDescription(params.getStopReason())); + return false; + } + + static void schedule(Context context) { + final JobScheduler jobScheduler = (JobScheduler) context.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + final JobInfo job = new JobInfo.Builder(IDLE_JOB_ID, + new ComponentName(context, BlobStoreIdleJobService.class)) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(IDLE_JOB_PERIOD_MILLIS) + .build(); + jobScheduler.schedule(job); + if (LOGV) { + Slog.v(TAG, "Scheduling the idle maintenance job"); + } + } +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java new file mode 100644 index 000000000000..5358245f517f --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerInternal.java @@ -0,0 +1,28 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.blob; + +/** + * BlobStoreManager local system service interface. + * + * Only for use within the system server. + */ +public abstract class BlobStoreManagerInternal { + /** + * Triggered from idle maintenance job to cleanup stale blobs and sessions. + */ + public abstract void onIdleMaintenance(); +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java index fcc30e30dfaa..0ba34cab6560 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -28,6 +28,8 @@ import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_NULL; import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; +import static com.android.server.blob.BlobStoreConfig.LOGV; +import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; @@ -40,6 +42,7 @@ import android.annotation.IdRes; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.blob.BlobHandle; import android.app.blob.IBlobStoreManager; import android.app.blob.IBlobStoreSession; @@ -49,6 +52,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManagerInternal; +import android.content.res.ResourceId; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -59,6 +63,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManagerInternal; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.ExceptionUtils; import android.util.LongSparseArray; @@ -67,6 +72,8 @@ import android.util.SparseArray; import android.util.Xml; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; @@ -90,7 +97,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.Set; /** * Service responsible for maintaining and facilitating access to data blobs published by apps. @@ -110,27 +120,47 @@ public class BlobStoreManagerService extends SystemService { @GuardedBy("mBlobsLock") private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>(); + // Contains all ids that are currently in use. + @GuardedBy("mBlobsLock") + private final ArraySet<Long> mKnownBlobIds = new ArraySet<>(); + private final Context mContext; private final Handler mHandler; + private final Injector mInjector; private final SessionStateChangeListener mSessionStateChangeListener = new SessionStateChangeListener(); private PackageManagerInternal mPackageManagerInternal; + private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo; + private final Runnable mSaveSessionsRunnable = this::writeBlobSessions; + public BlobStoreManagerService(Context context) { + this(context, new Injector()); + } + + @VisibleForTesting + BlobStoreManagerService(Context context, Injector injector) { super(context); + mContext = context; + mInjector = injector; + mHandler = mInjector.initializeMessageHandler(); + } + private static Handler initializeMessageHandler() { final HandlerThread handlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /* allowIo */); handlerThread.start(); - mHandler = new Handler(handlerThread.getLooper()); - Watchdog.getInstance().addThread(mHandler); + final Handler handler = new Handler(handlerThread.getLooper()); + Watchdog.getInstance().addThread(handler); + return handler; } @Override public void onStart() { publishBinderService(Context.BLOB_STORE_SERVICE, new Stub()); + LocalServices.addService(BlobStoreManagerInternal.class, new LocalService()); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); registerReceivers(); @@ -144,6 +174,8 @@ public class BlobStoreManagerService extends SystemService { readBlobSessionsLocked(allPackages); readBlobsInfoLocked(allPackages); } + } else if (phase == PHASE_BOOT_COMPLETED) { + BlobStoreIdleJobService.schedule(mContext); } } @@ -181,6 +213,54 @@ public class BlobStoreManagerService extends SystemService { return userBlobs; } + @VisibleForTesting + void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) { + synchronized (mBlobsLock) { + mSessions.put(userId, userSessions); + } + } + + @VisibleForTesting + void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) { + synchronized (mBlobsLock) { + mBlobsMap.put(userId, userBlobs); + } + } + + @VisibleForTesting + void addKnownIdsForTest(long... knownIds) { + synchronized (mBlobsLock) { + for (long id : knownIds) { + mKnownBlobIds.add(id); + } + } + } + + @VisibleForTesting + Set<Long> getKnownIdsForTest() { + synchronized (mBlobsLock) { + return mKnownBlobIds; + } + } + + @GuardedBy("mBlobsLock") + private void addSessionForUserLocked(BlobStoreSession session, int userId) { + getUserSessionsLocked(userId).put(session.getSessionId(), session); + mKnownBlobIds.add(session.getSessionId()); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) { + addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId)); + } + + @GuardedBy("mBlobsLock") + private void addBlobForUserLocked(BlobMetadata blobMetadata, + ArrayMap<BlobHandle, BlobMetadata> userBlobs) { + userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata); + mKnownBlobIds.add(blobMetadata.getBlobId()); + } + private long createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage) { synchronized (mBlobsLock) { @@ -189,7 +269,11 @@ public class BlobStoreManagerService extends SystemService { final BlobStoreSession session = new BlobStoreSession(mContext, sessionId, blobHandle, callingUid, callingPackage, mSessionStateChangeListener); - getUserSessionsLocked(UserHandle.getUserId(callingUid)).put(sessionId, session); + addSessionForUserLocked(session, UserHandle.getUserId(callingUid)); + if (LOGV) { + Slog.v(TAG, "Created session for " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); return sessionId; } @@ -217,7 +301,10 @@ public class BlobStoreManagerService extends SystemService { callingUid, callingPackage); session.open(); session.abandon(); - + if (LOGV) { + Slog.v(TAG, "Deleted session with id " + sessionId + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobSessionsAsync(); } } @@ -252,6 +339,10 @@ public class BlobStoreManagerService extends SystemService { } blobMetadata.addLeasee(callingPackage, callingUid, descriptionResId, leaseExpiryTimeMillis); + if (LOGV) { + Slog.v(TAG, "Acquired lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -267,6 +358,10 @@ public class BlobStoreManagerService extends SystemService { + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); } blobMetadata.removeLeasee(callingPackage, callingUid); + if (LOGV) { + Slog.v(TAG, "Released lease on " + blobHandle + + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); + } writeBlobsInfoAsync(); } } @@ -293,23 +388,27 @@ public class BlobStoreManagerService extends SystemService { case STATE_ABANDONED: case STATE_VERIFIED_INVALID: session.getSessionFile().delete(); - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)) - .remove(session.sessionId); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); + mKnownBlobIds.remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Session is invalid; deleted " + session); + } break; case STATE_COMMITTED: session.verifyBlobData(); break; case STATE_VERIFIED_VALID: - final int userId = UserHandle.getUserId(session.ownerUid); + final int userId = UserHandle.getUserId(session.getOwnerUid()); final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId); - BlobMetadata blob = userBlobs.get(session.blobHandle); + BlobMetadata blob = userBlobs.get(session.getBlobHandle()); if (blob == null) { blob = new BlobMetadata(mContext, - session.sessionId, session.blobHandle, userId); - userBlobs.put(session.blobHandle, blob); + session.getSessionId(), session.getBlobHandle(), userId); + addBlobForUserLocked(blob, userBlobs); } - final Committer newCommitter = new Committer(session.ownerPackageName, - session.ownerUid, session.getBlobAccessMode()); + final Committer newCommitter = new Committer(session.getOwnerPackageName(), + session.getOwnerUid(), session.getBlobAccessMode()); final Committer existingCommitter = blob.getExistingCommitter(newCommitter); blob.addCommitter(newCommitter); try { @@ -319,8 +418,11 @@ public class BlobStoreManagerService extends SystemService { blob.addCommitter(existingCommitter); session.sendCommitCallbackResult(COMMIT_RESULT_ERROR); } - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)) - .remove(session.sessionId); + getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid())) + .remove(session.getSessionId()); + if (LOGV) { + Slog.v(TAG, "Successfully committed session " + session); + } break; default: Slog.wtf(TAG, "Invalid session state: " @@ -363,6 +465,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_SESSIONS); out.endDocument(); sessionsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting sessions data"); + } } catch (Exception e) { sessionsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing sessions data", e); @@ -399,19 +504,22 @@ public class BlobStoreManagerService extends SystemService { continue; } final SparseArray<String> userPackages = allPackages.get( - UserHandle.getUserId(session.ownerUid)); + UserHandle.getUserId(session.getOwnerUid())); if (userPackages != null - && session.ownerPackageName.equals( - userPackages.get(session.ownerUid))) { - getUserSessionsLocked(UserHandle.getUserId(session.ownerUid)).put( - session.sessionId, session); + && session.getOwnerPackageName().equals( + userPackages.get(session.getOwnerUid()))) { + addSessionForUserLocked(session, + UserHandle.getUserId(session.getOwnerUid())); } else { // Unknown package or the session data does not belong to this package. session.getSessionFile().delete(); } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.sessionId); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading sessions data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading sessions data", e); } @@ -445,6 +553,9 @@ public class BlobStoreManagerService extends SystemService { out.endTag(null, TAG_BLOBS); out.endDocument(); blobsIndexFile.finishWrite(fos); + if (LOGV) { + Slog.v(TAG, "Finished persisting blobs data"); + } } catch (Exception e) { blobsIndexFile.failWrite(fos); Slog.wtf(TAG, "Error writing blobs data", e); @@ -476,18 +587,21 @@ public class BlobStoreManagerService extends SystemService { if (TAG_BLOB.equals(in.getName())) { final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); - final SparseArray<String> userPackages = allPackages.get(blobMetadata.userId); + final SparseArray<String> userPackages = allPackages.get( + blobMetadata.getUserId()); if (userPackages == null) { blobMetadata.getBlobFile().delete(); } else { - getUserBlobsLocked(blobMetadata.userId).put( - blobMetadata.blobHandle, blobMetadata); + addBlobForUserLocked(blobMetadata, blobMetadata.getUserId()); blobMetadata.removeInvalidCommitters(userPackages); blobMetadata.removeInvalidLeasees(userPackages); } - mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.blobId); + mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId()); } } + if (LOGV) { + Slog.v(TAG, "Finished reading blobs data"); + } } catch (Exception e) { Slog.wtf(TAG, "Error reading blobs data", e); } @@ -504,9 +618,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobsInfoAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobsInfo, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) { + mHandler.post(mSaveBlobsInfoRunnable); + } } private void writeBlobSessions() { @@ -520,9 +634,9 @@ public class BlobStoreManagerService extends SystemService { } private void writeBlobSessionsAsync() { - mHandler.post(PooledLambda.obtainRunnable( - BlobStoreManagerService::writeBlobSessions, - BlobStoreManagerService.this).recycleOnUse()); + if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) { + mHandler.post(mSaveSessionsRunnable); + } } private int getPackageUid(String packageName, int userId) { @@ -568,7 +682,8 @@ public class BlobStoreManagerService extends SystemService { return new AtomicFile(file, "blobs_index" /* commitLogTag */); } - private void handlePackageRemoved(String packageName, int uid) { + @VisibleForTesting + void handlePackageRemoved(String packageName, int uid) { synchronized (mBlobsLock) { // Clean up any pending sessions final LongSparseArray<BlobStoreSession> userSessions = @@ -576,25 +691,41 @@ public class BlobStoreManagerService extends SystemService { final ArrayList<Integer> indicesToRemove = new ArrayList<>(); for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); - if (session.ownerUid == uid - && session.ownerPackageName.equals(packageName)) { + if (session.getOwnerUid() == uid + && session.getOwnerPackageName().equals(packageName)) { session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); indicesToRemove.add(i); } } for (int i = 0, count = indicesToRemove.size(); i < count; ++i) { - userSessions.removeAt(i); + userSessions.removeAt(indicesToRemove.get(i)); } + writeBlobSessionsAsync(); // Remove the package from the committer and leasee list final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(UserHandle.getUserId(uid)); + indicesToRemove.clear(); for (int i = 0, count = userBlobs.size(); i < count; ++i) { final BlobMetadata blobMetadata = userBlobs.valueAt(i); blobMetadata.removeCommitter(packageName, uid); blobMetadata.removeLeasee(packageName, uid); + // Delete the blob if it doesn't have any active leases. + if (!blobMetadata.hasLeases()) { + blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + indicesToRemove.add(i); + } + } + for (int i = 0, count = indicesToRemove.size(); i < count; ++i) { + userBlobs.removeAt(indicesToRemove.get(i)); + } + writeBlobsInfoAsync(); + if (LOGV) { + Slog.v(TAG, "Removed blobs data associated with pkg=" + + packageName + ", uid=" + uid); } - // TODO: clean-up blobs which doesn't have any active leases. } } @@ -606,6 +737,7 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userSessions.size(); i < count; ++i) { final BlobStoreSession session = userSessions.valueAt(i); session.getSessionFile().delete(); + mKnownBlobIds.remove(session.getSessionId()); } } @@ -615,14 +747,187 @@ public class BlobStoreManagerService extends SystemService { for (int i = 0, count = userBlobs.size(); i < count; ++i) { final BlobMetadata blobMetadata = userBlobs.valueAt(i); blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + } + } + if (LOGV) { + Slog.v(TAG, "Removed blobs data in user " + userId); + } + } + } + + @GuardedBy("mBlobsLock") + @VisibleForTesting + void handleIdleMaintenanceLocked() { + // Cleanup any left over data on disk that is not part of index. + final ArrayList<Long> deletedBlobIds = new ArrayList<>(); + final ArrayList<File> filesToDelete = new ArrayList<>(); + final File blobsDir = BlobStoreConfig.getBlobsDir(); + if (blobsDir.exists()) { + for (File file : blobsDir.listFiles()) { + try { + final long id = Long.parseLong(file.getName()); + if (mKnownBlobIds.indexOf(id) < 0) { + filesToDelete.add(file); + deletedBlobIds.add(id); + } + } catch (NumberFormatException e) { + Slog.wtf(TAG, "Error parsing the file name: " + file, e); + filesToDelete.add(file); + } + } + for (int i = 0, count = filesToDelete.size(); i < count; ++i) { + filesToDelete.get(i).delete(); + } + } + + // Cleanup any stale blobs. + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + userBlobs.entrySet().removeIf(entry -> { + final BlobHandle blobHandle = entry.getKey(); + final BlobMetadata blobMetadata = entry.getValue(); + boolean shouldRemove = false; + + // Cleanup expired data blobs. + if (blobHandle.isExpired()) { + shouldRemove = true; + } + + // Cleanup blobs with no active leases. + // TODO: Exclude blobs which were just committed. + if (!blobMetadata.hasLeases()) { + shouldRemove = true; + } + + if (shouldRemove) { + blobMetadata.getBlobFile().delete(); + mKnownBlobIds.remove(blobMetadata.getBlobId()); + deletedBlobIds.add(blobMetadata.getBlobId()); + } + return shouldRemove; + }); + } + writeBlobsInfoAsync(); + + // Cleanup any stale sessions. + final ArrayList<Integer> indicesToRemove = new ArrayList<>(); + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + indicesToRemove.clear(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final BlobStoreSession blobStoreSession = userSessions.valueAt(j); + boolean shouldRemove = false; + + // Cleanup sessions which haven't been modified in a while. + if (blobStoreSession.getSessionFile().lastModified() + < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) { + shouldRemove = true; + } + + // Cleanup sessions with already expired data. + if (blobStoreSession.getBlobHandle().isExpired()) { + shouldRemove = true; } + + if (shouldRemove) { + blobStoreSession.getSessionFile().delete(); + mKnownBlobIds.remove(blobStoreSession.getSessionId()); + indicesToRemove.add(j); + deletedBlobIds.add(blobStoreSession.getSessionId()); + } + } + for (int j = 0; j < indicesToRemove.size(); ++j) { + userSessions.removeAt(indicesToRemove.get(j)); } } + if (LOGV) { + Slog.v(TAG, "Completed idle maintenance; deleted " + + Arrays.toString(deletedBlobIds.toArray())); + } + writeBlobSessionsAsync(); + } + + void runClearAllSessions(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mSessions.clear(); + } else { + mSessions.remove(userId); + } + writeBlobSessionsAsync(); + } + } + + void runClearAllBlobs(@UserIdInt int userId) { + synchronized (mBlobsLock) { + if (userId == UserHandle.USER_ALL) { + mBlobsMap.clear(); + } else { + mBlobsMap.remove(userId); + } + writeBlobsInfoAsync(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { + final int userId = mSessions.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); + fout.println("List of sessions in user #" + + userId + " (" + userSessions.size() + "):"); + fout.increaseIndent(); + for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { + final long sessionId = userSessions.keyAt(j); + final BlobStoreSession session = userSessions.valueAt(j); + if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(), + session.getOwnerUid(), session.getSessionId())) { + continue; + } + fout.println("Session #" + sessionId); + fout.increaseIndent(); + session.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } + } + + @GuardedBy("mBlobsLock") + private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) { + for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { + final int userId = mBlobsMap.keyAt(i); + if (!dumpArgs.shouldDumpUser(userId)) { + continue; + } + final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); + fout.println("List of blobs in user #" + + userId + " (" + userBlobs.size() + "):"); + fout.increaseIndent(); + for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { + final BlobMetadata blobMetadata = userBlobs.valueAt(j); + if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) { + continue; + } + fout.println("Blob #" + blobMetadata.getBlobId()); + fout.increaseIndent(); + blobMetadata.dump(fout, dumpArgs); + fout.decreaseIndent(); + } + fout.decreaseIndent(); + } } private class PackageChangedReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if (LOGV) { + Slog.v(TAG, "Received " + intent); + } switch (intent.getAction()) { case Intent.ACTION_PACKAGE_FULLY_REMOVED: case Intent.ACTION_PACKAGE_DATA_CLEARED: @@ -658,10 +963,9 @@ public class BlobStoreManagerService extends SystemService { @IntRange(from = 1) public long createSession(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - // TODO: verify blobHandle.algorithm is sha-256 - // TODO: assert blobHandle is valid. + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -682,7 +986,7 @@ public class BlobStoreManagerService extends SystemService { @NonNull String packageName) { Preconditions.checkArgumentPositive(sessionId, "sessionId must be positive: " + sessionId); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -695,7 +999,7 @@ public class BlobStoreManagerService extends SystemService { @NonNull String packageName) { Preconditions.checkArgumentPositive(sessionId, "sessionId must be positive: " + sessionId); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -706,8 +1010,9 @@ public class BlobStoreManagerService extends SystemService { @Override public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -727,24 +1032,27 @@ public class BlobStoreManagerService extends SystemService { @Override public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, - @CurrentTimeSecondsLong long leaseTimeoutSecs, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - Preconditions.checkArgumentPositive(descriptionResId, - "descriptionResId must be positive; value=" + descriptionResId); + @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Preconditions.checkArgument(ResourceId.isValid(descriptionResId), + "descriptionResId is not valid"); + Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, + "leaseExpiryTimeMillis must not be negative"); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - acquireLeaseInternal(blobHandle, descriptionResId, leaseTimeoutSecs, + acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis, callingUid, packageName); } @Override public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) { - Preconditions.checkNotNull(blobHandle, "blobHandle must not be null"); - Preconditions.checkNotNull(packageName, "packageName must not be null"); - + Objects.requireNonNull(blobHandle, "blobHandle must not be null"); + blobHandle.assertIsValid(); + Objects.requireNonNull(packageName, "packageName must not be null"); final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); @@ -754,7 +1062,7 @@ public class BlobStoreManagerService extends SystemService { @Override public void waitForIdle(@NonNull RemoteCallback remoteCallback) { - Preconditions.checkNotNull(remoteCallback, "remoteCallback must not be null"); + Objects.requireNonNull(remoteCallback, "remoteCallback must not be null"); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Caller is not allowed to call this; caller=" + Binder.getCallingUid()); @@ -766,47 +1074,213 @@ public class BlobStoreManagerService extends SystemService { public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) { // TODO: add proto-based version of this. - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return; + + final DumpArgs dumpArgs = DumpArgs.parse(args); final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " "); + if (dumpArgs.shouldDumpHelp()) { + writer.println("dumpsys blob_store [options]:"); + fout.increaseIndent(); + dumpArgs.dumpArgsUsage(fout); + fout.decreaseIndent(); + return; + } + synchronized (mBlobsLock) { fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId); fout.println(); - for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { - final int userId = mSessions.keyAt(i); - final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i); - fout.println("List of sessions in user #" - + userId + " (" + userSessions.size() + "):"); - fout.increaseIndent(); - for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) { - final long sessionId = userSessions.keyAt(j); - final BlobStoreSession session = userSessions.valueAt(j); - fout.println("Session #" + sessionId); - fout.increaseIndent(); - session.dump(fout); - fout.decreaseIndent(); - } - fout.decreaseIndent(); + + if (dumpArgs.shouldDumpSessions()) { + dumpSessionsLocked(fout, dumpArgs); + fout.println(); + } + if (dumpArgs.shouldDumpBlobs()) { + dumpBlobsLocked(fout, dumpArgs); + fout.println(); } + } + } + + @Override + public int handleShellCommand(@NonNull ParcelFileDescriptor in, + @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, + @NonNull String[] args) { + return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this, + in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args); + } + } + + static final class DumpArgs { + private boolean mDumpFull; + private final ArrayList<String> mDumpPackages = new ArrayList<>(); + private final ArrayList<Integer> mDumpUids = new ArrayList<>(); + private final ArrayList<Integer> mDumpUserIds = new ArrayList<>(); + private final ArrayList<Long> mDumpBlobIds = new ArrayList<>(); + private boolean mDumpOnlySelectedSections; + private boolean mDumpSessions; + private boolean mDumpBlobs; + private boolean mDumpHelp; + + public boolean shouldDumpSession(String packageName, int uid, long blobId) { + if (!CollectionUtils.isEmpty(mDumpPackages) + && mDumpPackages.indexOf(packageName) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpUids) + && mDumpUids.indexOf(uid) < 0) { + return false; + } + if (!CollectionUtils.isEmpty(mDumpBlobIds) + && mDumpBlobIds.indexOf(blobId) < 0) { + return false; + } + return true; + } - fout.print("\n\n"); - - for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { - final int userId = mBlobsMap.keyAt(i); - final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i); - fout.println("List of blobs in user #" - + userId + " (" + userBlobs.size() + "):"); - fout.increaseIndent(); - for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) { - final BlobMetadata blobMetadata = userBlobs.valueAt(j); - fout.println("Blob #" + blobMetadata.blobId); - fout.increaseIndent(); - blobMetadata.dump(fout); - fout.decreaseIndent(); + public boolean shouldDumpSessions() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpSessions; + } + + public boolean shouldDumpBlobs() { + if (!mDumpOnlySelectedSections) { + return true; + } + return mDumpBlobs; + } + + public boolean shouldDumpBlob(long blobId) { + return CollectionUtils.isEmpty(mDumpBlobIds) + || mDumpBlobIds.indexOf(blobId) >= 0; + } + + public boolean shouldDumpFull() { + return mDumpFull; + } + + public boolean shouldDumpUser(int userId) { + return CollectionUtils.isEmpty(mDumpUserIds) + || mDumpUserIds.indexOf(userId) >= 0; + } + + public boolean shouldDumpHelp() { + return mDumpHelp; + } + + private DumpArgs() {} + + public static DumpArgs parse(String[] args) { + final DumpArgs dumpArgs = new DumpArgs(); + if (args == null) { + return dumpArgs; + } + + for (int i = 0; i < args.length; ++i) { + final String opt = args[i]; + if ("--full".equals(opt) || "-f".equals(opt)) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { + dumpArgs.mDumpFull = true; } - fout.decreaseIndent(); + } else if ("--sessions".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpSessions = true; + } else if ("--blobs".equals(opt)) { + dumpArgs.mDumpOnlySelectedSections = true; + dumpArgs.mDumpBlobs = true; + } else if ("--package".equals(opt) || "-p".equals(opt)) { + dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName")); + } else if ("--uid".equals(opt) || "-u".equals(opt)) { + dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid")); + } else if ("--user".equals(opt)) { + dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId")); + } else if ("--blob".equals(opt) || "-b".equals(opt)) { + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId")); + } else if ("--help".equals(opt) || "-h".equals(opt)) { + dumpArgs.mDumpHelp = true; + } else { + // Everything else is assumed to be blob ids. + dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId")); } } + return dumpArgs; + } + + private static String getStringArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + return args[index]; + } + + private static int getIntArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final int value; + try { + value = Integer.parseInt(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; + } + + private static long getLongArgRequired(String[] args, int index, String argName) { + if (index >= args.length) { + throw new IllegalArgumentException("Missing " + argName); + } + final long value; + try { + value = Long.parseLong(args[index]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]); + } + return value; + } + + private void dumpArgsUsage(IndentingPrintWriter pw) { + pw.println("--help | -h"); + printWithIndent(pw, "Dump this help text"); + pw.println("--sessions"); + printWithIndent(pw, "Dump only the sessions info"); + pw.println("--blobs"); + printWithIndent(pw, "Dump only the committed blobs info"); + pw.println("--package | -p [package-name]"); + printWithIndent(pw, "Dump blobs info associated with the given package"); + pw.println("--uid | -u [uid]"); + printWithIndent(pw, "Dump blobs info associated with the given uid"); + pw.println("--user [user-id]"); + printWithIndent(pw, "Dump blobs info in the given user"); + pw.println("--blob | -b [session-id | blob-id]"); + printWithIndent(pw, "Dump blob info corresponding to the given ID"); + pw.println("--full | -f"); + printWithIndent(pw, "Dump full unredacted blobs data"); + } + + private void printWithIndent(IndentingPrintWriter pw, String str) { + pw.increaseIndent(); + pw.println(str); + pw.decreaseIndent(); + } + } + + private class LocalService extends BlobStoreManagerInternal { + @Override + public void onIdleMaintenance() { + synchronized (mBlobsLock) { + handleIdleMaintenanceLocked(); + } + } + } + + @VisibleForTesting + static class Injector { + public Handler initializeMessageHandler() { + return BlobStoreManagerService.initializeMessageHandler(); } } }
\ No newline at end of file diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java new file mode 100644 index 000000000000..3ac30f8fff6c --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java @@ -0,0 +1,111 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.blob; + +import android.os.ShellCommand; +import android.os.UserHandle; + +import java.io.PrintWriter; + +class BlobStoreManagerShellCommand extends ShellCommand { + + private final BlobStoreManagerService mService; + + BlobStoreManagerShellCommand(BlobStoreManagerService blobStoreManagerService) { + mService = blobStoreManagerService; + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(null); + } + final PrintWriter pw = getOutPrintWriter(); + switch (cmd) { + case "clear-all-sessions": + return runClearAllSessions(pw); + case "clear-all-blobs": + return runClearAllBlobs(pw); + default: + return handleDefaultCommands(cmd); + } + } + + private int runClearAllSessions(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllSessions(args.userId); + return 0; + } + + private int runClearAllBlobs(PrintWriter pw) { + final ParsedArgs args = new ParsedArgs(); + args.userId = UserHandle.USER_ALL; + + if (parseOptions(pw, args) < 0) { + return -1; + } + + mService.runClearAllBlobs(args.userId); + return 0; + } + + @Override + public void onHelp() { + final PrintWriter pw = getOutPrintWriter(); + pw.println("BlobStore service (blob_store) commands:"); + pw.println("help"); + pw.println(" Print this help text."); + pw.println(); + pw.println("clear-all-sessions [-u | --user USER_ID]"); + pw.println(" Remove all sessions."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's sessions to be removed;"); + pw.println(" If not specified, sessions in all users are removed."); + pw.println(); + pw.println("clear-all-blobs [-u | --user USER_ID]"); + pw.println(" Remove all blobs."); + pw.println(" Options:"); + pw.println(" -u or --user: specify which user's blobs to be removed;"); + pw.println(" If not specified, blobs in all users are removed."); + pw.println(); + } + + private int parseOptions(PrintWriter pw, ParsedArgs args) { + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "-u": + case "--user": + args.userId = Integer.parseInt(getNextArgRequired()); + break; + default: + pw.println("Error: unknown option '" + opt + "'"); + return -1; + } + } + return 0; + } + + private static class ParsedArgs { + public int userId; + } +} diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index 7d1c16653383..bd35b86babd8 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -21,11 +21,13 @@ import static android.app.blob.XmlTags.ATTR_PACKAGE; import static android.app.blob.XmlTags.ATTR_UID; import static android.app.blob.XmlTags.TAG_ACCESS_MODE; import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; +import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDONLY; import static android.system.OsConstants.O_RDWR; import static android.system.OsConstants.SEEK_SET; +import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.TAG; import android.annotation.BytesLong; @@ -40,6 +42,7 @@ import android.os.FileUtils; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.RevocableFileDescriptor; +import android.os.Trace; import android.os.storage.StorageManager; import android.system.ErrnoException; import android.system.Os; @@ -47,9 +50,11 @@ import android.util.ExceptionUtils; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; +import com.android.server.blob.BlobStoreManagerService.DumpArgs; import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; import org.xmlpull.v1.XmlPullParser; @@ -62,9 +67,11 @@ import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Objects; /** TODO: add doc */ -public class BlobStoreSession extends IBlobStoreSession.Stub { +@VisibleForTesting +class BlobStoreSession extends IBlobStoreSession.Stub { static final int STATE_OPENED = 1; static final int STATE_CLOSED = 0; @@ -78,10 +85,10 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { private final Context mContext; private final SessionStateChangeListener mListener; - public final BlobHandle blobHandle; - public final long sessionId; - public final int ownerUid; - public final String ownerPackageName; + private final BlobHandle mBlobHandle; + private final long mSessionId; + private final int mOwnerUid; + private final String mOwnerPackageName; // Do not access this directly, instead use getSessionFile(). private File mSessionFile; @@ -101,15 +108,31 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, int ownerUid, String ownerPackageName, SessionStateChangeListener listener) { this.mContext = context; - this.blobHandle = blobHandle; - this.sessionId = sessionId; - this.ownerUid = ownerUid; - this.ownerPackageName = ownerPackageName; + this.mBlobHandle = blobHandle; + this.mSessionId = sessionId; + this.mOwnerUid = ownerUid; + this.mOwnerPackageName = ownerPackageName; this.mListener = listener; } + public BlobHandle getBlobHandle() { + return mBlobHandle; + } + + public long getSessionId() { + return mSessionId; + } + + public int getOwnerUid() { + return mOwnerUid; + } + + public String getOwnerPackageName() { + return mOwnerPackageName; + } + boolean hasAccess(int callingUid, String callingPackageName) { - return ownerUid == callingUid && ownerPackageName.equals(callingPackageName); + return mOwnerUid == callingUid && mOwnerPackageName.equals(callingPackageName); } void open() { @@ -155,6 +178,8 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { @NonNull public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, @BytesLong long lengthBytes) { + Preconditions.checkArgumentNonnegative(offsetBytes, "offsetBytes must not be negative"); + assertCallerIsOwner(); synchronized (mSessionLock) { if (mState != STATE_OPENED) { @@ -242,7 +267,7 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) { assertCallerIsOwner(); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); synchronized (mSessionLock) { if (mState != STATE_OPENED) { throw new IllegalStateException("Not allowed to change access type in state: " @@ -280,7 +305,9 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { public boolean isPackageAccessAllowed(@NonNull String packageName, @NonNull byte[] certificate) { assertCallerIsOwner(); - Preconditions.checkNotNull(packageName, "packageName must not be null"); + Objects.requireNonNull(packageName, "packageName must not be null"); + Preconditions.checkByteArrayNotEmpty(certificate, "certificate"); + synchronized (mSessionLock) { if (mState != STATE_OPENED) { throw new IllegalStateException("Not allowed to get access type in state: " @@ -357,15 +384,22 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { void verifyBlobData() { byte[] actualDigest = null; try { - actualDigest = FileUtils.digest(getSessionFile(), blobHandle.algorithm); + Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, + "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length()); + actualDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); } catch (IOException | NoSuchAlgorithmException e) { Slog.e(TAG, "Error computing the digest", e); + } finally { + Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER); } synchronized (mSessionLock) { - if (actualDigest != null && Arrays.equals(actualDigest, blobHandle.digest)) { + if (actualDigest != null && Arrays.equals(actualDigest, mBlobHandle.digest)) { mState = STATE_VERIFIED_VALID; // Commit callback will be sent once the data is persisted. } else { + if (LOGV) { + Slog.v(TAG, "Digest of the data didn't match the given BlobHandle.digest"); + } mState = STATE_VERIFIED_INVALID; sendCommitCallbackResult(COMMIT_RESULT_ERROR); } @@ -401,7 +435,7 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { @Nullable File getSessionFile() { if (mSessionFile == null) { - mSessionFile = BlobStoreConfig.prepareBlobFile(sessionId); + mSessionFile = BlobStoreConfig.prepareBlobFile(mSessionId); } return mSessionFile; } @@ -423,22 +457,32 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { } } + @Override + public String toString() { + return "BlobStoreSession {" + + "id:" + mSessionId + + ",handle:" + mBlobHandle + + ",uid:" + mOwnerUid + + ",pkg:" + mOwnerPackageName + + "}"; + } + private void assertCallerIsOwner() { final int callingUid = Binder.getCallingUid(); - if (callingUid != ownerUid) { - throw new SecurityException(ownerUid + " is not the session owner"); + if (callingUid != mOwnerUid) { + throw new SecurityException(mOwnerUid + " is not the session owner"); } } - void dump(IndentingPrintWriter fout) { + void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { synchronized (mSessionLock) { fout.println("state: " + stateToString(mState)); - fout.println("ownerUid: " + ownerUid); - fout.println("ownerPkg: " + ownerPackageName); + fout.println("ownerUid: " + mOwnerUid); + fout.println("ownerPkg: " + mOwnerPackageName); fout.println("blobHandle:"); fout.increaseIndent(); - blobHandle.dump(fout); + mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); fout.decreaseIndent(); fout.println("accessMode:"); @@ -452,12 +496,12 @@ public class BlobStoreSession extends IBlobStoreSession.Stub { void writeToXml(@NonNull XmlSerializer out) throws IOException { synchronized (mSessionLock) { - XmlUtils.writeLongAttribute(out, ATTR_ID, sessionId); - XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, ownerPackageName); - XmlUtils.writeIntAttribute(out, ATTR_UID, ownerUid); + XmlUtils.writeLongAttribute(out, ATTR_ID, mSessionId); + XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, mOwnerPackageName); + XmlUtils.writeIntAttribute(out, ATTR_UID, mOwnerUid); out.startTag(null, TAG_BLOB_HANDLE); - blobHandle.writeToXml(out); + mBlobHandle.writeToXml(out); out.endTag(null, TAG_BLOB_HANDLE); out.startTag(null, TAG_ACCESS_MODE); diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 0bb07caf0b00..088cadba89ab 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1287,7 +1287,7 @@ public class JobInfo implements Parcelable { * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}. To continually monitor * for content changes, you need to schedule a new JobInfo observing the same URIs * before you finish execution of the JobService handling the most recent changes. - * Following this pattern will ensure you do not lost any content changes: while your + * Following this pattern will ensure you do not lose any content changes: while your * job is running, the system will continue monitoring for content changes, and propagate * any it sees over to the next job you schedule.</p> * diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 69f4748548a7..939164edd13e 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -494,9 +494,10 @@ public class JobSchedulerService extends com.android.server.SystemService private static final String DEPRECATED_KEY_BG_LOW_JOB_COUNT = "bg_low_job_count"; private static final String DEPRECATED_KEY_BG_CRITICAL_JOB_COUNT = "bg_critical_job_count"; - private static final String KEY_MAX_STANDARD_RESCHEDULE_COUNT + private static final String DEPRECATED_KEY_MAX_STANDARD_RESCHEDULE_COUNT = "max_standard_reschedule_count"; - private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; + private static final String DEPRECATED_KEY_MAX_WORK_RESCHEDULE_COUNT = + "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; private static final String DEPRECATED_KEY_STANDBY_HEARTBEAT_TIME = @@ -525,8 +526,6 @@ public class JobSchedulerService extends com.android.server.SystemService private static final long DEFAULT_MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = 31 * MINUTE_IN_MILLIS; private static final float DEFAULT_HEAVY_USE_FACTOR = .9f; private static final float DEFAULT_MODERATE_USE_FACTOR = .5f; - private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; - private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f; @@ -640,16 +639,6 @@ public class JobSchedulerService extends com.android.server.SystemService "screen_off_job_concurrency_increase_delay_ms", 30_000); /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for standard jobs. - */ - int MAX_STANDARD_RESCHEDULE_COUNT = DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT; - /** - * The maximum number of times we allow a job to have itself rescheduled before - * giving up on it, for jobs that are executing work. - */ - int MAX_WORK_RESCHEDULE_COUNT = DEFAULT_MAX_WORK_RESCHEDULE_COUNT; - /** * The minimum backoff time to allow for linear backoff. */ long MIN_LINEAR_BACKOFF_TIME = DEFAULT_MIN_LINEAR_BACKOFF_TIME; @@ -735,10 +724,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.parse(mParser); - MAX_STANDARD_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_STANDARD_RESCHEDULE_COUNT, - DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT); - MAX_WORK_RESCHEDULE_COUNT = mParser.getInt(KEY_MAX_WORK_RESCHEDULE_COUNT, - DEFAULT_MAX_WORK_RESCHEDULE_COUNT); MIN_LINEAR_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_LINEAR_BACKOFF_TIME, DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getDurationMillis(KEY_MIN_EXP_BACKOFF_TIME, @@ -790,8 +775,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dump(pw, ""); - pw.printPair(KEY_MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT).println(); - pw.printPair(KEY_MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT).println(); pw.printPair(KEY_MIN_LINEAR_BACKOFF_TIME, MIN_LINEAR_BACKOFF_TIME).println(); pw.printPair(KEY_MIN_EXP_BACKOFF_TIME, MIN_EXP_BACKOFF_TIME).println(); pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println(); @@ -827,8 +810,6 @@ public class JobSchedulerService extends com.android.server.SystemService SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS.dumpProto(proto, ConstantsProto.SCREEN_OFF_JOB_CONCURRENCY_INCREASE_DELAY_MS); - proto.write(ConstantsProto.MAX_STANDARD_RESCHEDULE_COUNT, MAX_STANDARD_RESCHEDULE_COUNT); - proto.write(ConstantsProto.MAX_WORK_RESCHEDULE_COUNT, MAX_WORK_RESCHEDULE_COUNT); proto.write(ConstantsProto.MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME); proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME); proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC); @@ -1390,18 +1371,8 @@ public class JobSchedulerService extends com.android.server.SystemService // Effective standby bucket can change after this in some situations so use // the real bucket so that the job is tracked by the controllers. if (js.getStandbyBucket() == RESTRICTED_INDEX) { - js.addDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.addDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.addDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).startTrackingRestrictedJobLocked(js); } else { - js.removeDynamicConstraint(JobStatus.CONSTRAINT_BATTERY_NOT_LOW); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CHARGING); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_CONNECTIVITY); - js.removeDynamicConstraint(JobStatus.CONSTRAINT_IDLE); - mRestrictiveControllers.get(j).stopTrackingRestrictedJobLocked(js); } } @@ -1738,19 +1709,6 @@ public class JobSchedulerService extends com.android.server.SystemService final int backoffAttempts = failureToReschedule.getNumFailures() + 1; long delayMillis; - if (failureToReschedule.hasWorkLocked()) { - if (backoffAttempts > mConstants.MAX_WORK_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > work limit " - + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - } else if (backoffAttempts > mConstants.MAX_STANDARD_RESCHEDULE_COUNT) { - Slog.w(TAG, "Not rescheduling " + failureToReschedule + ": attempt #" - + backoffAttempts + " > std limit " + mConstants.MAX_STANDARD_RESCHEDULE_COUNT); - return null; - } - switch (job.getBackoffPolicy()) { case JobInfo.BACKOFF_POLICY_LINEAR: { long backoff = initialBackoffMillis; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index f706260edec2..1e89158ca4bb 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -17,6 +17,8 @@ package com.android.server.job.controllers; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; +import static com.android.server.job.JobSchedulerService.NEVER_INDEX; +import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import android.app.AppGlobals; @@ -63,26 +65,36 @@ import java.util.function.Predicate; * @hide */ public final class JobStatus { - static final String TAG = "JobSchedulerService"; + private static final String TAG = "JobScheduler.JobStatus"; static final boolean DEBUG = JobSchedulerService.DEBUG; public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE; public static final long NO_EARLIEST_RUNTIME = 0L; - public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 - public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 - public static final int CONSTRAINT_BATTERY_NOT_LOW = - JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 + static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0 + static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE; // 1 << 2 + static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1 static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3 static final int CONSTRAINT_TIMING_DELAY = 1<<31; static final int CONSTRAINT_DEADLINE = 1<<30; - public static final int CONSTRAINT_CONNECTIVITY = 1 << 28; + static final int CONSTRAINT_CONNECTIVITY = 1 << 28; static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26; static final int CONSTRAINT_DEVICE_NOT_DOZING = 1 << 25; // Implicit constraint static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint /** + * The additional set of dynamic constraints that must be met if the job's effective bucket is + * {@link JobSchedulerService#RESTRICTED_INDEX}. Connectivity can be ignored if the job doesn't + * need network. + */ + private static final int DYNAMIC_RESTRICTED_CONSTRAINTS = + CONSTRAINT_BATTERY_NOT_LOW + | CONSTRAINT_CHARGING + | CONSTRAINT_CONNECTIVITY + | CONSTRAINT_IDLE; + + /** * The constraints that we want to log to statsd. * * Constraints that can be inferred from other atoms have been excluded to avoid logging too @@ -419,7 +431,11 @@ public final class JobStatus { this.requiredConstraints = requiredConstraints; mRequiredConstraintsOfInterest = requiredConstraints & CONSTRAINTS_OF_INTEREST; mReadyNotDozing = (job.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0; - mReadyDynamicSatisfied = true; + if (standbyBucket == RESTRICTED_INDEX) { + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else { + mReadyDynamicSatisfied = true; + } mLastSuccessfulRunTime = lastSuccessfulRunTime; mLastFailedRunTime = lastFailedRunTime; @@ -727,6 +743,14 @@ public final class JobStatus { } public void setStandbyBucket(int newBucket) { + if (newBucket == RESTRICTED_INDEX) { + // Adding to the bucket. + addDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } else if (standbyBucket == RESTRICTED_INDEX) { + // Removing from the RESTRICTED bucket. + removeDynamicConstraints(DYNAMIC_RESTRICTED_CONSTRAINTS); + } + standbyBucket = newBucket; } @@ -1054,6 +1078,11 @@ public final class JobStatus { if (old == state) { return false; } + if (DEBUG) { + Slog.v(TAG, + "Constraint " + constraint + " is " + (!state ? "NOT " : "") + "satisfied for " + + toShortString()); + } satisfiedConstraints = (satisfiedConstraints&~constraint) | (state ? constraint : 0); mSatisfiedConstraintsOfInterest = satisfiedConstraints & CONSTRAINTS_OF_INTEREST; mReadyDynamicSatisfied = @@ -1086,38 +1115,40 @@ public final class JobStatus { } /** - * Indicates that this job cannot run without the specified constraint. This is evaluated + * Indicates that this job cannot run without the specified constraints. This is evaluated * separately from the job's explicitly requested constraints and MUST be satisfied before * the job can run if the app doesn't have quota. - * */ - public void addDynamicConstraint(int constraint) { - if (constraint == CONSTRAINT_WITHIN_QUOTA) { + private void addDynamicConstraints(int constraints) { + if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) { + // Quota should never be used as a dynamic constraint. Slog.wtf(TAG, "Tried to set quota as a dynamic constraint"); - return; + constraints &= ~CONSTRAINT_WITHIN_QUOTA; } // Connectivity and content trigger are special since they're only valid to add if the // job has requested network or specific content URIs. Adding these constraints to jobs // that don't need them doesn't make sense. - if ((constraint == CONSTRAINT_CONNECTIVITY && !hasConnectivityConstraint()) - || (constraint == CONSTRAINT_CONTENT_TRIGGER && !hasContentTriggerConstraint())) { - return; + if (!hasConnectivityConstraint()) { + constraints &= ~CONSTRAINT_CONNECTIVITY; + } + if (!hasContentTriggerConstraint()) { + constraints &= ~CONSTRAINT_CONTENT_TRIGGER; } - mDynamicConstraints |= constraint; + mDynamicConstraints |= constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } /** - * Removes a dynamic constraint from a job, meaning that the requirement is not required for + * Removes dynamic constraints from a job, meaning that the requirements are not required for * the job to run (if the job itself hasn't requested the constraint. This is separate from * the job's explicitly requested constraints and does not remove those requested constraints. * */ - public void removeDynamicConstraint(int constraint) { - mDynamicConstraints &= ~constraint; + private void removeDynamicConstraints(int constraints) { + mDynamicConstraints &= ~constraints; mReadyDynamicSatisfied = mDynamicConstraints == (satisfiedConstraints & mDynamicConstraints); } @@ -1193,7 +1224,11 @@ public final class JobStatus { private boolean isReady(int satisfiedConstraints) { // Quota and dynamic constraints trump all other constraints. - if (!mReadyWithinQuota && !mReadyDynamicSatisfied) { + // NEVER jobs are not supposed to run at all. Since we're using quota to allow parole + // sessions (exempt from dynamic restrictions), we need the additional check to ensure + // that NEVER jobs don't run. + // TODO: cleanup quota and standby bucket management so we don't need the additional checks + if ((!mReadyWithinQuota && !mReadyDynamicSatisfied) || standbyBucket == NEVER_INDEX) { return false; } // Deadline constraint trumps other constraints besides quota and dynamic (except for diff --git a/apex/media/framework/Android.bp b/apex/media/framework/Android.bp index 2266d049d70a..23ae8afc1694 100644 --- a/apex/media/framework/Android.bp +++ b/apex/media/framework/Android.bp @@ -38,6 +38,12 @@ java_library { "android.media", ], + optimize: { + enabled: true, + shrink: true, + proguard_flags_files: ["updatable-media-proguard.flags"], + }, + installable: true, // TODO: build against stable API surface. Use core_platform for now to avoid @@ -99,13 +105,10 @@ filegroup { path: "java" } -droidstubs { - name: "updatable-media-stubs", - srcs: [ - ":updatable-media-srcs", - ":framework-media-annotation-srcs", - ], - defaults: [ "framework-module-stubs-defaults-systemapi" ], +stubs_defaults { + name: "framework-media-stubs-srcs-defaults", + srcs: [ ":updatable-media-srcs" ], + libs: [ "framework_media_annotation" ], aidl: { // TODO(b/135922046) remove this include_dirs: ["frameworks/base/core/java"], @@ -113,9 +116,53 @@ droidstubs { sdk_version: "system_current", } +droidstubs { + name: "framework-media-stubs-srcs-publicapi", + defaults: [ + "framework-media-stubs-srcs-defaults", + "framework-module-stubs-defaults-publicapi", + ], +} + +droidstubs { + name: "framework-media-stubs-srcs-systemapi", + defaults: [ + "framework-media-stubs-srcs-defaults", + "framework-module-stubs-defaults-systemapi", + ], +} + +droidstubs { + name: "framework-media-api-module_libs_api", + defaults: [ + "framework-media-stubs-srcs-defaults", + "framework-module-api-defaults-module_libs_api", + ], +} + +droidstubs { + name: "framework-media-stubs-srcs-module_libs_api", + defaults: [ + "framework-media-stubs-srcs-defaults", + "framework-module-stubs-defaults-module_libs_api", + ], +} + +java_library { + name: "framework-media-stubs-publicapi", + srcs: [":framework-media-stubs-srcs-publicapi"], + sdk_version: "current", +} + +java_library { + name: "framework-media-stubs-systemapi", + srcs: [":framework-media-stubs-srcs-systemapi"], + sdk_version: "system_current", +} + java_library { - name: "updatable_media_stubs", - srcs: [":updatable-media-stubs"], + name: "framework-media-stubs-module_libs_api", + srcs: [":framework-media-stubs-srcs-module_libs_api"], sdk_version: "system_current", } diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java index d59270c6a51b..96110e1541f8 100644 --- a/apex/media/framework/java/android/media/MediaParser.java +++ b/apex/media/framework/java/android/media/MediaParser.java @@ -15,6 +15,7 @@ */ package android.media; +import android.annotation.CheckResult; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.Uri; @@ -32,6 +33,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap.SeekPoints; import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.amr.AmrExtractor; +import com.google.android.exoplayer2.extractor.flac.FlacExtractor; import com.google.android.exoplayer2.extractor.flv.FlvExtractor; import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; @@ -382,6 +384,7 @@ public final class MediaParser { * parse the input. */ @NonNull + @CheckResult private static UnrecognizedInputFormatException createForExtractors( @NonNull String... extractorNames) { StringBuilder builder = new StringBuilder(); @@ -536,7 +539,7 @@ public final class MediaParser { } } if (mExtractor == null) { - UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); + throw UnrecognizedInputFormatException.createForExtractors(mExtractorNamesPool); } return true; } @@ -912,6 +915,7 @@ public final class MediaParser { extractorFactoriesByName.put("exo.Ac4Extractor", Ac4Extractor::new); extractorFactoriesByName.put("exo.AdtsExtractor", AdtsExtractor::new); extractorFactoriesByName.put("exo.AmrExtractor", AmrExtractor::new); + extractorFactoriesByName.put("exo.FlacExtractor", FlacExtractor::new); extractorFactoriesByName.put("exo.FlvExtractor", FlvExtractor::new); extractorFactoriesByName.put("exo.FragmentedMp4Extractor", FragmentedMp4Extractor::new); extractorFactoriesByName.put("exo.MatroskaExtractor", MatroskaExtractor::new); diff --git a/apex/media/framework/updatable-media-proguard.flags b/apex/media/framework/updatable-media-proguard.flags new file mode 100644 index 000000000000..4e7d8422bf44 --- /dev/null +++ b/apex/media/framework/updatable-media-proguard.flags @@ -0,0 +1,2 @@ +# Keep all symbols in android.media. +-keep class android.media.* {*;} diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp index 09571a1cd111..126fa00a31f0 100644 --- a/apex/permission/framework/Android.bp +++ b/apex/permission/framework/Android.bp @@ -44,23 +44,66 @@ java_library { ], } +stubs_defaults { + name: "framework-permission-stubs-defaults", + srcs: [ ":framework-permission-sources" ], + libs: [ "framework-annotations-lib" ], + sdk_version: "system_current", +} + droidstubs { - name: "framework-permission-stubs-sources", - srcs: [ - ":framework-annotations", - ":framework-permission-sources", + name: "framework-permission-stubs-srcs-publicapi", + sdk_version: "system_current", + defaults: [ + "framework-module-stubs-defaults-publicapi", + "framework-permission-stubs-defaults", ], +} + +droidstubs { + name: "framework-permission-stubs-srcs-systemapi", sdk_version: "system_current", defaults: [ "framework-module-stubs-defaults-systemapi", + "framework-permission-stubs-defaults", ], } -java_library { - name: "framework-permission-stubs", - srcs: [ - ":framework-permission-stubs-sources", +droidstubs { + name: "framework-permission-api-module_libs_api", + sdk_version: "system_current", + defaults: [ + "framework-module-api-defaults-module_libs_api", + "framework-permission-stubs-defaults", + ], +} + +droidstubs { + name: "framework-permission-stubs-srcs-module_libs_api", + sdk_version: "system_current", + defaults: [ + "framework-module-stubs-defaults-module_libs_api", + "framework-permission-stubs-defaults", ], +} + +java_library { + name: "framework-permission-stubs-publicapi", + srcs: [ ":framework-permission-stubs-srcs-publicapi" ], + sdk_version: "system_current", + installable: false, +} + +java_library { + name: "framework-permission-stubs-systemapi", + srcs: [ ":framework-permission-stubs-srcs-systemapi" ], + sdk_version: "system_current", + installable: false, +} + +java_library { + name: "framework-permission-stubs-module_libs_api", + srcs: [ ":framework-permission-stubs-srcs-module_libs_api" ], sdk_version: "system_current", installable: false, } diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java index 5f2d94441965..6c7f82a11908 100644 --- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsPersistence.java @@ -19,6 +19,7 @@ package com.android.permission.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; import android.os.UserHandle; /** @@ -27,7 +28,7 @@ import android.os.UserHandle; * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +@SystemApi(client = Client.SYSTEM_SERVER) public interface RuntimePermissionsPersistence { /** diff --git a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java index 2a939e51b98e..cd2750a0bee5 100644 --- a/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java +++ b/apex/permission/service/java/com/android/permission/persistence/RuntimePermissionsState.java @@ -19,6 +19,7 @@ package com.android.permission.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; import java.util.List; import java.util.Map; @@ -29,7 +30,7 @@ import java.util.Map; * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +@SystemApi(client = Client.SYSTEM_SERVER) public final class RuntimePermissionsState { /** diff --git a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java index 63c8eedd6285..2908a3872df9 100644 --- a/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java +++ b/apex/permission/service/java/com/android/role/persistence/RolesPersistence.java @@ -19,6 +19,7 @@ package com.android.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; import android.os.UserHandle; /** @@ -27,7 +28,7 @@ import android.os.UserHandle; * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +@SystemApi(client = Client.SYSTEM_SERVER) public interface RolesPersistence { /** diff --git a/apex/permission/service/java/com/android/role/persistence/RolesState.java b/apex/permission/service/java/com/android/role/persistence/RolesState.java index bff980e2e126..7da9d11f172f 100644 --- a/apex/permission/service/java/com/android/role/persistence/RolesState.java +++ b/apex/permission/service/java/com/android/role/persistence/RolesState.java @@ -19,6 +19,7 @@ package com.android.role.persistence; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.SystemApi.Client; import java.util.Map; import java.util.Set; @@ -29,7 +30,7 @@ import java.util.Set; * TODO(b/147914847): Remove @hide when it becomes the default. * @hide */ -@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES, process = SystemApi.Process.SYSTEM_SERVER) +@SystemApi(client = Client.SYSTEM_SERVER) public final class RolesState { /** diff --git a/apex/permission/testing/Android.bp b/apex/permission/testing/Android.bp index f8978dcd83ee..63bf0a08e956 100644 --- a/apex/permission/testing/Android.bp +++ b/apex/permission/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.permission", visibility: [ "//system/apex/tests", diff --git a/apex/sdkextensions/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp index 6fb7ef43416e..900193a6bd0d 100644 --- a/apex/sdkextensions/derive_sdk/derive_sdk.cpp +++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp @@ -69,7 +69,7 @@ int main(int, char**) { auto itr = std::min_element(versions.begin(), versions.end()); std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr); - if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) { + if (!android::base::SetProperty("build.version.extensions.r", prop_value)) { LOG(ERROR) << "failed to set sdk_info prop"; return EXIT_FAILURE; } diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp index 245a96b99148..86f4ab7c1128 100644 --- a/apex/sdkextensions/framework/Android.bp +++ b/apex/sdkextensions/framework/Android.bp @@ -44,34 +44,68 @@ java_library { ], } +stubs_defaults { + name: "framework-sdkextensions-stubs-defaults", + srcs: [ ":framework-sdkextensions-sources" ], + libs: [ "framework-annotations-lib" ], + sdk_version: "system_current", +} + droidstubs { - name: "framework-sdkextensions-droidstubs-publicapi", + name: "framework-sdkextensions-stubs-srcs-publicapi", defaults: [ - "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-publicapi", + "framework-sdkextensions-stubs-defaults", ] } droidstubs { - name: "framework-sdkextensions-droidstubs-systemapi", + name: "framework-sdkextensions-stubs-srcs-systemapi", defaults: [ - "framework-sdkextensions-stubs-defaults", "framework-module-stubs-defaults-systemapi", + "framework-sdkextensions-stubs-defaults", ] } -stubs_defaults { - name: "framework-sdkextensions-stubs-defaults", - srcs: [ - ":framework-sdkextensions-sources", - ":framework-annotations", - ], - sdk_version: "system_current", +droidstubs { + name: "framework-sdkextensions-api-module_libs_api", + defaults: [ + "framework-module-api-defaults-module_libs_api", + "framework-sdkextensions-stubs-defaults", + ] +} + +droidstubs { + name: "framework-sdkextensions-stubs-srcs-module_libs_api", + defaults: [ + "framework-module-stubs-defaults-module_libs_api", + "framework-sdkextensions-stubs-defaults", + ] +} + +java_library { + name: "framework-sdkextensions-stubs-publicapi", + srcs: [":framework-sdkextensions-stubs-srcs-publicapi"], + sdk_version: "current", + visibility: [ + "//frameworks/base", // Framework + "//frameworks/base/apex/sdkextensions", // sdkextensions SDK + ] } java_library { name: "framework-sdkextensions-stubs-systemapi", - srcs: [":framework-sdkextensions-droidstubs-systemapi"], + srcs: [":framework-sdkextensions-stubs-srcs-systemapi"], + sdk_version: "system_current", + visibility: [ + "//frameworks/base", // Framework + "//frameworks/base/apex/sdkextensions", // sdkextensions SDK + ] +} + +java_library { + name: "framework-sdkextensions-stubs-module_libs_api", + srcs: [":framework-sdkextensions-stubs-srcs-module_libs_api"], sdk_version: "system_current", visibility: [ "//frameworks/base", // Framework diff --git a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java index a8a7effa9b6c..103b53e81db5 100644 --- a/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java @@ -38,7 +38,7 @@ public class SdkExtensions { private static final int R_EXTENSION_INT; static { - R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0); + R_EXTENSION_INT = SystemProperties.getInt("build.version.extensions.r", 0); } /** diff --git a/apex/sdkextensions/framework/java/android/os/ext/test/Test.java b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java new file mode 100644 index 000000000000..1715f498be1e --- /dev/null +++ b/apex/sdkextensions/framework/java/android/os/ext/test/Test.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.ext.test; + +import android.annotation.SystemApi; + +/** + * This class exists temporarily to verify SDK updates are working properly. + * @deprecated Do not use. + */ +@Deprecated +public class Test { + + public Test() { } + + /** @hide */ + public void testA() {} + + /** @hide */ + public void testB() {} + + /** @hide */ + public void testC() {} + + /** @hide */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void testD() {} + + public void testE() {} + + /** @hide */ + @SystemApi + public void testF() {} + + /** @hide */ + @SystemApi + public void testG() {} + +} diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp index e6451cc29bc2..f2f5b321fafe 100644 --- a/apex/sdkextensions/testing/Android.bp +++ b/apex/sdkextensions/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.sdkext", visibility: [ "//system/apex/tests" ], defaults: ["com.android.sdkext-defaults"], diff --git a/apex/statsd/Android.bp b/apex/statsd/Android.bp index 09ca1d257460..1f9f18cd051a 100644 --- a/apex/statsd/Android.bp +++ b/apex/statsd/Android.bp @@ -19,8 +19,9 @@ apex { } apex_defaults { - // libc.so and libcutils.so are included in the apex - // native_shared_libs: ["libc", "libcutils"], + native_shared_libs: [ + "libstats_jni", + ], // binaries: ["vold"], java_libs: [ "framework-statsd", @@ -44,3 +45,33 @@ android_app_certificate { // com.android.os.statsd.pk8 (the private key) certificate: "com.android.os.statsd", } + + +// JNI library for StatsLog.write +cc_library_shared { + name: "libstats_jni", + srcs: ["jni/**/*.cpp"], + shared_libs: [ + "libnativehelper", // Has stable abi - should not be copied into apex. + "liblog", // Has a stable abi - should not be copied into apex. + ], + static_libs: [ + //TODO: make shared - need libstatssocket to also live in the apex. + "libstatssocket", + "libcutils", // TODO: remove - needed by libstatssocket + ], + //TODO: is libc++_static correct? + stl: "libc++_static", + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + "-Wno-unused-parameter", + ], + apex_available: [ + "com.android.os.statsd", + "test_com.android.os.statsd", + //TODO (b/148620413): remove platform. + "//apex_available:platform", + ], +}
\ No newline at end of file diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp index 6d639fddd043..db5f439cd40e 100644 --- a/apex/statsd/aidl/Android.bp +++ b/apex/statsd/aidl/Android.bp @@ -13,35 +13,31 @@ // See the License for the specific language governing permissions and // limitations under the License. // - -// TODO(b/145815909): move StatsDimensionsValue.aidl here filegroup { - name: "statsd_aidl", + name: "statsd_java_aidl", + srcs: ["**/*.aidl"], +} + +aidl_interface { + name: "statsd-aidl", srcs: [ "android/os/IPendingIntentRef.aidl", "android/os/IPullAtomCallback.aidl", "android/os/IPullAtomResultReceiver.aidl", "android/os/IStatsCompanionService.aidl", "android/os/IStatsd.aidl", + "android/os/StatsDimensionsValueParcel.aidl", "android/util/StatsEventParcel.aidl", ], -} - -filegroup { - name: "statsd_java_aidl", - srcs: ["**/*.aidl"], -} - -// This library is currently unused -aidl_interface { - name: "stats-event-parcel-aidl", - srcs: ["android/util/StatsEventParcel.aidl"], backend: { java: { - sdk_version: "28", + enabled: false, // the platform uses statsd_java_aidl }, cpp: { - enabled: false, + enabled: true, + }, + ndk: { + enabled: true, } } } diff --git a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl index 6b9e467a7e15..000a69992a49 100644 --- a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl +++ b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl @@ -16,7 +16,7 @@ package android.os; -import android.os.StatsDimensionsValue; +import android.os.StatsDimensionsValueParcel; /** * Binder interface to hold a PendingIntent for StatsCompanionService. @@ -42,5 +42,5 @@ interface IPendingIntentRef { */ oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, long subscriptionRuleId, in String[] cookies, - in StatsDimensionsValue dimensionsValue); -}
\ No newline at end of file + in StatsDimensionsValueParcel dimensionsValueParcel); +} diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl index bdd1da7bf3d3..b94928f09ae0 100644 --- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl +++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl @@ -61,4 +61,11 @@ interface IStatsCompanionService { /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */ oneway void triggerUidSnapshot(); + + /** + * Ask StatsCompanionService if the given permission is allowed for a particular process + * and user ID. statsd is incapable of doing this check itself because checkCallingPermission + * is not currently supported by libbinder_ndk. + */ + boolean checkPermission(String permission, int pid, int uid); } diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl index a2564212366f..10b1e5b26d12 100644 --- a/apex/statsd/aidl/android/os/IStatsd.aidl +++ b/apex/statsd/aidl/android/os/IStatsd.aidl @@ -222,18 +222,6 @@ interface IStatsd { const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04; /** - * Logs an event for binary push for module updates. - */ - oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode, - in int options, in int state, in long[] experimentId); - - /** - * Logs an event for watchdog rollbacks. - */ - oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName, - in long packageVersionCode, in int rollbackReason, in String failingPackageName); - - /** * Returns the most recently registered experiment IDs. */ long[] getRegisteredExperimentIds(); diff --git a/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl new file mode 100644 index 000000000000..a8685e34dd52 --- /dev/null +++ b/apex/statsd/aidl/android/os/StatsDimensionsValueParcel.aidl @@ -0,0 +1,21 @@ +package android.os; + +/** + * @hide + */ +parcelable StatsDimensionsValueParcel { + /** + * Field equals: + * - atomTag for top level StatsDimensionsValueParcel + * - position in dimension for all other levels + */ + int field; + int valueType; + + String stringValue; + int intValue; + long longValue; + boolean boolValue; + float floatValue; + StatsDimensionsValueParcel[] tupleValue; +} diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp index f66f0340edab..80def475efb9 100644 --- a/apex/statsd/framework/Android.bp +++ b/apex/statsd/framework/Android.bp @@ -12,12 +12,28 @@ // See the License for the specific language governing permissions and // limitations under the License. +genrule { + name: "statslog-statsd-java-gen", + tools: ["stats-log-api-gen"], + cmd: "$(location stats-log-api-gen) --java $(out) --module statsd" + + " --javaPackage com.android.internal.util --javaClass StatsdStatsLog", + out: ["com/android/internal/util/StatsdStatsLog.java"], +} + +java_library_static { + name: "statslog-statsd", + srcs: [ + ":statslog-statsd-java-gen", + ], +} + filegroup { name: "framework-statsd-sources", srcs: [ - "java/**/*.java", + "java/**/*.java", + ":statsd_java_aidl", + ":statslog-statsd-java-gen", ], - path: "java", } java_library { @@ -30,20 +46,20 @@ java_library { ], permitted_packages: [ "android.app", + "android.os", "android.util", ], libs: [ "framework-annotations-lib", - // TODO(b/146230220): Use framework-system-stubs instead. - //"android_system_stubs_current", - //"framework_module_lib_stubs_current", + // TODO(b/146230220): Use android_module_lib_stubs_current instead. + //"android_module_lib_stubs_current", "framework-all", ], hostdex: true, // for hiddenapi check visibility: [ "//frameworks/base/apex/statsd:__subpackages__", - //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs - "//frameworks/base", + //TODO(b/146167933) remove this + "//frameworks/opt/net/wifi/service", ], apex_available: [ "com.android.os.statsd", @@ -51,31 +67,69 @@ java_library { ], } -droidstubs { - name: "framework-statsd-stubs-docs", - defaults: [ - "framework-module-stubs-defaults-systemapi" - ], - srcs: [ - ":framework-annotations", - ":framework-statsd-sources", - ], +stubs_defaults { + name: "framework-statsd-stubs-srcs-defaults", + srcs: [ ":framework-statsd-sources" ], libs: [ // TODO(b/148218250): Change to android_system_stubs_current "framework-all", + "framework-annotations-lib", ], sdk_version: "core_platform", } -// TODO(b/146167933): Use these stubs in frameworks/base/Android.bp -java_library { - name: "framework-statsd-stubs", - srcs: [ - ":framework-statsd-stubs-docs", +droidstubs { + name: "framework-statsd-stubs-srcs-publicapi", + defaults: [ + "framework-module-stubs-defaults-systemapi", + "framework-statsd-stubs-srcs-defaults", ], - libs: [ - // TODO(b/148218250): Change to android_system_stubs_current - "framework-all", +} + +droidstubs { + name: "framework-statsd-stubs-srcs-systemapi", + defaults: [ + "framework-module-stubs-defaults-systemapi", + "framework-statsd-stubs-srcs-defaults", + ], +} + +droidstubs { + name: "framework-statsd-api-module_libs_api", + defaults: [ + "framework-module-api-defaults-module_libs_api", + "framework-statsd-stubs-srcs-defaults", + ], +} + +droidstubs { + name: "framework-statsd-stubs-srcs-module_libs_api", + defaults: [ + "framework-module-stubs-defaults-module_libs_api", + "framework-statsd-stubs-srcs-defaults", ], +} + +java_library { + name: "framework-statsd-stubs-publicapi", + srcs: [ ":framework-statsd-stubs-srcs-publicapi" ], + // TODO(b/148218250): Change to current + libs: [ "framework-all" ], + sdk_version: "core_platform", +} + +java_library { + name: "framework-statsd-stubs-systemapi", + srcs: [ ":framework-statsd-stubs-srcs-systemapi" ], + // TODO(b/148218250): Change to system_current + libs: [ "framework-all" ], + sdk_version: "core_platform", +} + +java_library { + name: "framework-statsd-stubs-module_libs_api", + srcs: [ ":framework-statsd-stubs-srcs-systemapi" ], + // TODO(b/148218250): Change to system_current + libs: [ "framework-all" ], sdk_version: "core_platform", } diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java index a1de330c300a..526d17ff0d71 100644 --- a/apex/statsd/framework/java/android/app/StatsManager.java +++ b/apex/statsd/framework/java/android/app/StatsManager.java @@ -32,7 +32,7 @@ import android.os.IStatsd; import android.os.RemoteException; import android.os.StatsFrameworkInitializer; import android.util.AndroidException; -import android.util.Slog; +import android.util.Log; import android.util.StatsEvent; import android.util.StatsEventParcel; @@ -155,7 +155,7 @@ public final class StatsManager { // can throw IllegalArgumentException service.addConfiguration(configKey, config, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding configuration"); + Log.e(TAG, "Failed to connect to statsmanager when adding configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -191,7 +191,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); service.removeConfiguration(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when removing configuration"); + Log.e(TAG, "Failed to connect to statsmanager when removing configuration"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -258,7 +258,7 @@ public final class StatsManager { mContext.getOpPackageName()); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", + Log.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber", e); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -311,7 +311,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when registering data listener."); + Log.e(TAG, "Failed to connect to statsmanager when registering data listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -348,7 +348,7 @@ public final class StatsManager { } } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager " + Log.e(TAG, "Failed to connect to statsmanager " + "when registering active configs listener."); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { @@ -387,7 +387,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getData(configKey, mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting data"); + Log.e(TAG, "Failed to connect to statsmanager when getting data"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -424,7 +424,7 @@ public final class StatsManager { IStatsManagerService service = getIStatsManagerServiceLocked(); return service.getMetadata(mContext.getOpPackageName()); } catch (RemoteException e) { - Slog.e(TAG, "Failed to connect to statsmanager when getting metadata"); + Log.e(TAG, "Failed to connect to statsmanager when getting metadata"); throw new StatsUnavailableException("could not connect", e); } catch (SecurityException e) { throw new StatsUnavailableException(e.getMessage(), e); @@ -464,7 +464,7 @@ public final class StatsManager { return service.getRegisteredExperimentIds(); } catch (RemoteException e) { if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Failed to connect to StatsManagerService when getting " + "registered experiment IDs"); } @@ -476,7 +476,7 @@ public final class StatsManager { /** * Registers a callback for an atom when that atom is to be pulled. The stats service will * invoke pullData in the callback when the stats service determines that this atom needs to be - * pulled. + * pulled. This method should not be called by third-party apps. * * @param atomTag The tag of the atom for this puller callback. * @param metadata Optional metadata specifying the timeout, cool down time, and @@ -485,6 +485,7 @@ public final class StatsManager { * @param executor The executor in which to run the callback. * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata, @NonNull @CallbackExecutor Executor executor, @NonNull StatsPullAtomCallback callback) { @@ -510,11 +511,12 @@ public final class StatsManager { /** * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing - * pulls will still occur. + * pulls will still occur. This method should not be called by third-party apps. * * @param atomTag The tag of the atom of which to unregister * */ + @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int atomTag) { synchronized (sLock) { try { @@ -553,7 +555,7 @@ public final class StatsManager { try { resultReceiver.pullFinished(atomTag, success, parcels); } catch (RemoteException e) { - Slog.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); + Log.w(TAG, "StatsPullResultReceiver failed for tag " + mAtomId); } }); } finally { diff --git a/apex/statsd/framework/java/android/os/StatsDimensionsValue.java b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java new file mode 100644 index 000000000000..35273da96413 --- /dev/null +++ b/apex/statsd/framework/java/android/os/StatsDimensionsValue.java @@ -0,0 +1,395 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.os; + +import android.annotation.SystemApi; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Container for statsd dimension value information, corresponding to a + * stats_log.proto's DimensionValue. + * + * This consists of a field (an int representing a statsd atom field) + * and a value (which may be one of a number of types). + * + * <p> + * Only a single value is held, and it is necessarily one of the following types: + * {@link String}, int, long, boolean, float, + * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}). + * + * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the + * following ints, depending on the type of value: + * <ul> + * <li>{@link #STRING_VALUE_TYPE}</li> + * <li>{@link #INT_VALUE_TYPE}</li> + * <li>{@link #LONG_VALUE_TYPE}</li> + * <li>{@link #BOOLEAN_VALUE_TYPE}</li> + * <li>{@link #FLOAT_VALUE_TYPE}</li> + * <li>{@link #TUPLE_VALUE_TYPE}</li> + * </ul> + * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants + * as a parameter. + * The value itself can be retrieved using the correct get...Value() function for its type. + * + * <p> + * The field is always an int, and always exists; it can be obtained using {@link #getField()}. + * + * + * @hide + */ +@SystemApi +public final class StatsDimensionsValue implements Parcelable { + private static final String TAG = "StatsDimensionsValue"; + + // Values of the value type correspond to stats_log.proto's DimensionValue fields. + // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h. + /** Indicates that this holds a String. */ + public static final int STRING_VALUE_TYPE = 2; + /** Indicates that this holds an int. */ + public static final int INT_VALUE_TYPE = 3; + /** Indicates that this holds a long. */ + public static final int LONG_VALUE_TYPE = 4; + /** Indicates that this holds a boolean. */ + public static final int BOOLEAN_VALUE_TYPE = 5; + /** Indicates that this holds a float. */ + public static final int FLOAT_VALUE_TYPE = 6; + /** Indicates that this holds a List of StatsDimensionsValues. */ + public static final int TUPLE_VALUE_TYPE = 7; + + /** Value of a stats_log.proto DimensionsValue.field. */ + private final int mField; + + /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */ + private final int mValueType; + + /** + * Value of a stats_log.proto DimensionsValue.value. + * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[]. + */ + private final Object mValue; // immutable or array of immutables + + /** + * Creates a {@code StatsDimensionValue} from a parcel. + * + * @hide + */ + public StatsDimensionsValue(Parcel in) { + mField = in.readInt(); + mValueType = in.readInt(); + mValue = readValueFromParcel(mValueType, in); + } + + /** + * Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel + * TODO(b/149103391): Make StatsDimensionsValue a wrapper on top of + * StatsDimensionsValueParcel. + * + * @hide + */ + public StatsDimensionsValue(StatsDimensionsValueParcel parcel) { + mField = parcel.field; + mValueType = parcel.valueType; + switch (mValueType) { + case STRING_VALUE_TYPE: + mValue = parcel.stringValue; + break; + case INT_VALUE_TYPE: + mValue = parcel.intValue; + break; + case LONG_VALUE_TYPE: + mValue = parcel.longValue; + break; + case BOOLEAN_VALUE_TYPE: + mValue = parcel.boolValue; + break; + case FLOAT_VALUE_TYPE: + mValue = parcel.floatValue; + break; + case TUPLE_VALUE_TYPE: + StatsDimensionsValue[] values = new StatsDimensionsValue[parcel.tupleValue.length]; + for (int i = 0; i < parcel.tupleValue.length; i++) { + values[i] = new StatsDimensionsValue(parcel.tupleValue[i]); + } + mValue = values; + break; + default: + Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType); + mValue = null; + break; + } + } + + + /** + * Return the field, i.e. the tag of a statsd atom. + * + * @return the field + */ + public int getField() { + return mField; + } + + /** + * Retrieve the String held, if any. + * + * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE}, + * null otherwise + */ + public String getStringValue() { + try { + if (mValueType == STRING_VALUE_TYPE) return (String) mValue; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return null; + } + + /** + * Retrieve the int held, if any. + * + * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise + */ + public int getIntValue() { + try { + if (mValueType == INT_VALUE_TYPE) return (Integer) mValue; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the long held, if any. + * + * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise + */ + public long getLongValue() { + try { + if (mValueType == LONG_VALUE_TYPE) return (Long) mValue; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the boolean held, if any. + * + * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE}, + * false otherwise + */ + public boolean getBooleanValue() { + try { + if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return false; + } + + /** + * Retrieve the float held, if any. + * + * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise + */ + public float getFloatValue() { + try { + if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return 0; + } + + /** + * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held, + * if any. + * + * @return the {@link List} of {@link StatsDimensionsValue} held + * if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE}, + * null otherwise + */ + public List<StatsDimensionsValue> getTupleValueList() { + if (mValueType != TUPLE_VALUE_TYPE) { + return null; + } + try { + StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue; + List<StatsDimensionsValue> copy = new ArrayList<>(orig.length); + // Shallow copy since StatsDimensionsValue is immutable anyway + for (int i = 0; i < orig.length; i++) { + copy.add(orig[i]); + } + return copy; + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + return null; + } + } + + /** + * Returns the constant representing the type of value stored, namely one of + * <ul> + * <li>{@link #STRING_VALUE_TYPE}</li> + * <li>{@link #INT_VALUE_TYPE}</li> + * <li>{@link #LONG_VALUE_TYPE}</li> + * <li>{@link #BOOLEAN_VALUE_TYPE}</li> + * <li>{@link #FLOAT_VALUE_TYPE}</li> + * <li>{@link #TUPLE_VALUE_TYPE}</li> + * </ul> + * + * @return the constant representing the type of value stored + */ + public int getValueType() { + return mValueType; + } + + /** + * Returns whether the type of value stored is equal to the given type. + * + * @param valueType int representing the type of value stored, as used in {@link #getValueType} + * @return true if {@link #getValueType()} is equal to {@code valueType}. + */ + public boolean isValueType(int valueType) { + return mValueType == valueType; + } + + /** + * Returns a String representing the information in this StatsDimensionValue. + * No guarantees are made about the format of this String. + * + * @return String representation + * + * @hide + */ + // Follows the format of statsd's dimension.h toString. + public String toString() { + try { + StringBuilder sb = new StringBuilder(); + sb.append(mField); + sb.append(":"); + if (mValueType == TUPLE_VALUE_TYPE) { + sb.append("{"); + StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue; + for (int i = 0; i < sbvs.length; i++) { + sb.append(sbvs[i].toString()); + sb.append("|"); + } + sb.append("}"); + } else { + sb.append(mValue.toString()); + } + return sb.toString(); + } catch (ClassCastException e) { + Log.w(TAG, "Failed to successfully get value", e); + } + return ""; + } + + /** + * Parcelable Creator for StatsDimensionsValue. + */ + public static final @android.annotation.NonNull + Parcelable.Creator<StatsDimensionsValue> CREATOR = new + Parcelable.Creator<StatsDimensionsValue>() { + public StatsDimensionsValue createFromParcel(Parcel in) { + return new StatsDimensionsValue(in); + } + + public StatsDimensionsValue[] newArray(int size) { + return new StatsDimensionsValue[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mField); + out.writeInt(mValueType); + writeValueToParcel(mValueType, mValue, out, flags); + } + + /** Writes mValue to a parcel. Returns true if succeeds. */ + private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) { + try { + switch (valueType) { + case STRING_VALUE_TYPE: + out.writeString((String) value); + return true; + case INT_VALUE_TYPE: + out.writeInt((Integer) value); + return true; + case LONG_VALUE_TYPE: + out.writeLong((Long) value); + return true; + case BOOLEAN_VALUE_TYPE: + out.writeBoolean((Boolean) value); + return true; + case FLOAT_VALUE_TYPE: + out.writeFloat((Float) value); + return true; + case TUPLE_VALUE_TYPE: { + StatsDimensionsValue[] values = (StatsDimensionsValue[]) value; + out.writeInt(values.length); + for (int i = 0; i < values.length; i++) { + values[i].writeToParcel(out, flags); + } + return true; + } + default: + Log.w(TAG, "readValue of an impossible type " + valueType); + return false; + } + } catch (ClassCastException e) { + Log.w(TAG, "writeValue cast failed", e); + return false; + } + } + + /** Reads mValue from a parcel. */ + private static Object readValueFromParcel(int valueType, Parcel parcel) { + switch (valueType) { + case STRING_VALUE_TYPE: + return parcel.readString(); + case INT_VALUE_TYPE: + return parcel.readInt(); + case LONG_VALUE_TYPE: + return parcel.readLong(); + case BOOLEAN_VALUE_TYPE: + return parcel.readBoolean(); + case FLOAT_VALUE_TYPE: + return parcel.readFloat(); + case TUPLE_VALUE_TYPE: { + final int sz = parcel.readInt(); + StatsDimensionsValue[] values = new StatsDimensionsValue[sz]; + for (int i = 0; i < sz; i++) { + values[i] = new StatsDimensionsValue(parcel); + } + return values; + } + default: + Log.w(TAG, "readValue of an impossible type " + valueType); + return null; + } + } +} diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java new file mode 100644 index 000000000000..511bc01f521e --- /dev/null +++ b/apex/statsd/framework/java/android/util/StatsLog.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2017 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 android.util; + +import static android.Manifest.permission.DUMP; +import static android.Manifest.permission.PACKAGE_USAGE_STATS; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; +import android.content.Context; +import android.os.IStatsd; +import android.os.RemoteException; +import android.os.StatsFrameworkInitializer; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.util.StatsdStatsLog; + +/** + * StatsLog provides an API for developers to send events to statsd. The events can be used to + * define custom metrics inside statsd. + */ +public final class StatsLog { + private static final String TAG = "StatsLog"; + private static final boolean DEBUG = false; + private static final int EXPERIMENT_IDS_FIELD_ID = 1; + + private static IStatsd sService; + + private static Object sLogLock = new Object(); + + private StatsLog() { + } + + /** + * Logs a start event. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logStart(int label) { + synchronized (sLogLock) { + try { + IStatsd service = getIStatsdLocked(); + if (service == null) { + if (DEBUG) { + Log.d(TAG, "Failed to find statsd when logging start"); + } + return false; + } + service.sendAppBreadcrumbAtom(label, + StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) { + Log.d(TAG, "Failed to connect to statsd when logging start"); + } + return false; + } + } + } + + /** + * Logs a stop event. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logStop(int label) { + synchronized (sLogLock) { + try { + IStatsd service = getIStatsdLocked(); + if (service == null) { + if (DEBUG) { + Log.d(TAG, "Failed to find statsd when logging stop"); + } + return false; + } + service.sendAppBreadcrumbAtom( + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) { + Log.d(TAG, "Failed to connect to statsd when logging stop"); + } + return false; + } + } + } + + /** + * Logs an event that does not represent a start or stop boundary. + * + * @param label developer-chosen label. + * @return True if the log request was sent to statsd. + */ + public static boolean logEvent(int label) { + synchronized (sLogLock) { + try { + IStatsd service = getIStatsdLocked(); + if (service == null) { + if (DEBUG) { + Log.d(TAG, "Failed to find statsd when logging event"); + } + return false; + } + service.sendAppBreadcrumbAtom( + label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED); + return true; + } catch (RemoteException e) { + sService = null; + if (DEBUG) { + Log.d(TAG, "Failed to connect to statsd when logging event"); + } + return false; + } + } + } + + /** + * Logs an event for binary push for module updates. + * + * @param trainName name of install train. + * @param trainVersionCode version code of the train. + * @param options optional flags about this install. + * The last 3 bits indicate options: + * 0x01: FLAG_REQUIRE_STAGING + * 0x02: FLAG_ROLLBACK_ENABLED + * 0x04: FLAG_REQUIRE_LOW_LATENCY_MONITOR + * @param state current install state. Defined as State enums in + * BinaryPushStateChanged atom in + * frameworks/base/cmds/statsd/src/atoms.proto + * @param experimentIds experiment ids. + * @return True if the log request was sent to statsd. + */ + @RequiresPermission(allOf = {DUMP, PACKAGE_USAGE_STATS}) + public static boolean logBinaryPushStateChanged(@NonNull String trainName, + long trainVersionCode, int options, int state, + @NonNull long[] experimentIds) { + ProtoOutputStream proto = new ProtoOutputStream(); + for (long id : experimentIds) { + proto.write( + ProtoOutputStream.FIELD_TYPE_INT64 + | ProtoOutputStream.FIELD_COUNT_REPEATED + | EXPERIMENT_IDS_FIELD_ID, + id); + } + StatsdStatsLog.write(StatsdStatsLog.BINARY_PUSH_STATE_CHANGED, + trainName, + trainVersionCode, + (options & IStatsd.FLAG_REQUIRE_STAGING) > 0, + (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0, + (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0, + state, + proto.getBytes(), + 0, + 0, + false); + return true; + } + + private static IStatsd getIStatsdLocked() throws RemoteException { + if (sService != null) { + return sService; + } + sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer + .getStatsServiceManager() + .getStatsdServiceRegisterer() + .get()); + return sService; + } + + /** + * Write an event to stats log using the raw format. + * + * @param buffer The encoded buffer of data to write. + * @param size The number of bytes from the buffer to write. + * @hide + */ + // TODO(b/144935988): Mark deprecated. + @SystemApi + public static void writeRaw(@NonNull byte[] buffer, int size) { + // TODO(b/144935988): make this no-op once clients have migrated to StatsEvent. + writeImpl(buffer, size, 0); + } + + /** + * Write an event to stats log using the raw format. + * + * @param buffer The encoded buffer of data to write. + * @param size The number of bytes from the buffer to write. + * @param atomId The id of the atom to which the event belongs. + */ + private static native void writeImpl(@NonNull byte[] buffer, int size, int atomId); + + /** + * Write an event to stats log using the raw format encapsulated in StatsEvent. + * After writing to stats log, release() is called on the StatsEvent object. + * No further action should be taken on the StatsEvent object following this call. + * + * @param statsEvent The StatsEvent object containing the encoded buffer of data to write. + * @hide + */ + @SystemApi + public static void write(@NonNull final StatsEvent statsEvent) { + writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId()); + statsEvent.release(); + } + + private static void enforceDumpCallingPermission(Context context) { + context.enforceCallingPermission(android.Manifest.permission.DUMP, "Need DUMP permission."); + } + + private static void enforcesageStatsCallingPermission(Context context) { + context.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, + "Need PACKAGE_USAGE_STATS permission."); + } +} diff --git a/apex/statsd/jni/android_util_StatsLog.cpp b/apex/statsd/jni/android_util_StatsLog.cpp new file mode 100644 index 000000000000..9d410eb1f836 --- /dev/null +++ b/apex/statsd/jni/android_util_StatsLog.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 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. + */ + +#define LOG_NAMESPACE "StatsLog.tag." +#define LOG_TAG "StatsLog_println" + +#include "jni.h" +#include <log/log.h> +#include <nativehelper/JNIHelp.h> +#include "stats_buffer_writer.h" + +namespace android { + +static void android_util_StatsLog_write(JNIEnv* env, jobject clazz, jbyteArray buf, jint size, + jint atomId) { + if (buf == NULL) { + return; + } + jint actualSize = env->GetArrayLength(buf); + if (actualSize < size) { + return; + } + + jbyte* bufferArray = env->GetByteArrayElements(buf, NULL); + if (bufferArray == NULL) { + return; + } + + write_buffer_to_statsd((void*) bufferArray, size, atomId); + + env->ReleaseByteArrayElements(buf, bufferArray, 0); +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "writeImpl", "([BII)V", (void*) android_util_StatsLog_write }, +}; + +int register_android_util_StatsLog(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "android/util/StatsLog", gMethods, NELEM(gMethods)); +} +}; // namespace android + +/* + * JNI Initialization + */ +jint JNI_OnLoad(JavaVM* jvm, void* reserved) { + JNIEnv* e; + int status; + + ALOGV("statsd : loading JNI\n"); + // Check JNI version + if (jvm->GetEnv((void**)&e, JNI_VERSION_1_4)) { + ALOGE("JNI version mismatch error"); + return JNI_ERR; + } + status = android::register_android_util_StatsLog(e); + if (status < 0) { + ALOGE("jni statsd registration failure, status: %d", status); + return JNI_ERR; + } + return JNI_VERSION_1_4; +} diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp index 910384845810..0f8a151eed7c 100644 --- a/apex/statsd/service/Android.bp +++ b/apex/statsd/service/Android.bp @@ -1,17 +1,30 @@ // Statsd Service jar, which will eventually be put in the statsd mainline apex. // service-statsd needs to be added to PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS. // This jar will contain StatsCompanionService + +filegroup { + name: "service-statsd-sources", + srcs: [ + "java/**/*.java", + ], +} + java_library { name: "service-statsd", installable: true, srcs: [ - "java/**/*.java", + ":service-statsd-sources", ], - // TODO: link against the proper stubs (b/146084685). + // TODO(b/146209659): Use system_current instead once framework-statsd compiles against + // system_current. + sdk_version: "core_platform", libs: [ - "framework-minus-apex", - "services.core", + "framework-annotations-lib", + "framework-statsd", + // TODO(b/146758669): Remove this line after nullability annotations are system APIs. + "android_system_stubs_current", + "services-stubs", ], apex_available: [ "com.android.os.statsd", diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java index 4383b503bfe7..c1ba73f03c06 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java @@ -24,7 +24,8 @@ import android.os.Binder; import android.os.IPendingIntentRef; import android.os.Process; import android.os.StatsDimensionsValue; -import android.util.Slog; +import android.os.StatsDimensionsValueParcel; +import android.util.Log; import com.android.server.SystemService; @@ -38,11 +39,18 @@ public class StatsCompanion { private static final String TAG = "StatsCompanion"; private static final boolean DEBUG = false; - static void enforceStatsCompanionPermission(Context context) { + private static final int AID_STATSD = 1066; + + private static final String STATS_COMPANION_SERVICE = "statscompanion"; + private static final String STATS_MANAGER_SERVICE = "statsmanager"; + + static void enforceStatsdCallingUid() { if (Binder.getCallingPid() == Process.myPid()) { return; } - context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null); + if (Binder.getCallingUid() != AID_STATSD) { + throw new SecurityException("Not allowed to access StatsCompanion"); + } } /** @@ -64,14 +72,12 @@ public class StatsCompanion { mStatsManagerService.setStatsCompanionService(mStatsCompanionService); try { - publishBinderService(Context.STATS_COMPANION_SERVICE, - mStatsCompanionService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE); - publishBinderService(Context.STATS_MANAGER_SERVICE, - mStatsManagerService); - if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_MANAGER_SERVICE); + publishBinderService(STATS_COMPANION_SERVICE, mStatsCompanionService); + if (DEBUG) Log.d(TAG, "Published " + STATS_COMPANION_SERVICE); + publishBinderService(STATS_MANAGER_SERVICE, mStatsManagerService); + if (DEBUG) Log.d(TAG, "Published " + STATS_MANAGER_SERVICE); } catch (Exception e) { - Slog.e(TAG, "Failed to publishBinderService", e); + Log.e(TAG, "Failed to publishBinderService", e); } } @@ -114,35 +120,37 @@ public class StatsCompanion { @Override public void sendDataBroadcast(long lastReportTimeNs) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs); try { mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send PendingIntent"); + Log.w(TAG, "Unable to send PendingIntent"); } } @Override public void sendActiveConfigsChangedBroadcast(long[] configIds) { - enforceStatsCompanionPermission(mContext); + enforceStatsdCallingUid(); Intent intent = new Intent(); intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds); try { mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null); if (DEBUG) { - Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); + Log.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds)); } } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); + Log.w(TAG, "Unable to send active configs changed broadcast using PendingIntent"); } } @Override public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId, - long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) { - enforceStatsCompanionPermission(mContext); + long subscriptionRuleId, String[] cookies, + StatsDimensionsValueParcel dimensionsValueParcel) { + enforceStatsdCallingUid(); + StatsDimensionsValue dimensionsValue = new StatsDimensionsValue(dimensionsValueParcel); Intent intent = new Intent() .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid) @@ -158,7 +166,7 @@ public class StatsCompanion { StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, String.format( "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}", configUid, configId, subscriptionId, subscriptionRuleId, @@ -168,7 +176,7 @@ public class StatsCompanion { try { mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null); } catch (PendingIntent.CanceledException e) { - Slog.w(TAG, + Log.w(TAG, "Unable to send using PendingIntent from uid " + configUid + "; presumably it had been cancelled."); } diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java index 1e92826ee8a0..cb167c30e30f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java @@ -40,14 +40,10 @@ import android.os.StatsFrameworkInitializer; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.util.Slog; +import android.util.Log; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; -import com.android.internal.os.LooperStats; -import com.android.internal.util.DumpUtils; -import com.android.server.BinderCallsStatsService; -import com.android.server.LocalServices; import libcore.io.IoUtils; @@ -89,6 +85,12 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public static final int DEATH_THRESHOLD = 10; + // TODO(b/149090705): Implement an alternative to sending broadcast with @hide flag + // FLAG_RECEIVER_INCLUDE_BACKGROUND. Instead of using the flag, find the + // list of registered broadcast receivers and send them directed broadcasts + // to wake them up. See b/147374337. + private static final int FLAG_RECEIVER_INCLUDE_BACKGROUND = 0x01000000; + static final class CompanionHandler extends Handler { CompanionHandler(Looper looper) { super(looper); @@ -126,7 +128,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public void onReceive(Context context, Intent intent) { synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd for UserUpdateReceiver"); + Log.w(TAG, "Could not access statsd for UserUpdateReceiver"); return; } try { @@ -134,14 +136,14 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { // Needed since the new user basically has a version of every app. informAllUidsLocked(context); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd latest update of all apps", e); + Log.e(TAG, "Failed to inform statsd latest update of all apps", e); forgetEverythingLocked(); } } } }; mShutdownEventReceiver = new ShutdownEventReceiver(); - if (DEBUG) Slog.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); + if (DEBUG) Log.d(TAG, "Registered receiver for ACTION_PACKAGE_REPLACED and ADDED."); HandlerThread handlerThread = new HandlerThread(TAG); handlerThread.start(); mHandler = new CompanionHandler(handlerThread.getLooper()); @@ -171,21 +173,21 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { PackageManager pm = context.getPackageManager(); final List<UserHandle> users = um.getUserHandles(true); if (DEBUG) { - Slog.d(TAG, "Iterating over " + users.size() + " userHandles."); + Log.d(TAG, "Iterating over " + users.size() + " userHandles."); } ParcelFileDescriptor[] fds; try { fds = ParcelFileDescriptor.createPipe(); } catch (IOException e) { - Slog.e(TAG, "Failed to create a pipe to send uid map data.", e); + Log.e(TAG, "Failed to create a pipe to send uid map data.", e); return; } sStatsd.informAllUidData(fds[0]); try { fds[0].close(); } catch (IOException e) { - Slog.e(TAG, "Failed to close the read side of the pipe.", e); + Log.e(TAG, "Failed to close the read side of the pipe.", e); } final ParcelFileDescriptor writeFd = fds[1]; HandlerThread backgroundThread = new HandlerThread( @@ -239,7 +241,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } output.flush(); if (DEBUG) { - Slog.d(TAG, "Sent data for " + numRecords + " apps"); + Log.d(TAG, "Sent data for " + numRecords + " apps"); } } finally { IoUtils.closeQuietly(fout); @@ -261,10 +263,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { return; // Keep only replacing or normal add and remove. } - if (DEBUG) Slog.d(TAG, "StatsCompanionService noticed an app was updated."); + if (DEBUG) Log.d(TAG, "StatsCompanionService noticed an app was updated."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of an app update"); + Log.w(TAG, "Could not access statsd to inform it of an app update"); return; } try { @@ -299,7 +301,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { installer == null ? "" : installer); } } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of an app update", e); + Log.w(TAG, "Failed to inform statsd of an app update", e); } } } @@ -308,18 +310,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { public final static class AnomalyAlarmListener implements OnAlarmListener { @Override public void onAlarm() { - Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + Log.i(TAG, "StatsCompanionService believes an anomaly has occurred at time " + System.currentTimeMillis() + "ms."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); + Log.w(TAG, "Could not access statsd to inform it of anomaly alarm firing"); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAnomalyAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); + Log.w(TAG, "Failed to inform statsd of anomaly alarm firing", e); } } // AlarmManager releases its own wakelock here. @@ -330,18 +332,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to poll something."); + Log.d(TAG, "Time to poll something."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of pulling alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informPollAlarmFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of pulling alarm firing.", e); } } } @@ -351,18 +353,18 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override public void onAlarm() { if (DEBUG) { - Slog.d(TAG, "Time to trigger periodic alarm."); + Log.d(TAG, "Time to trigger periodic alarm."); } synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); + Log.w(TAG, "Could not access statsd to inform it of periodic alarm firing."); return; } try { // Two-way call to statsd to retain AlarmManager wakelock sStatsd.informAlarmForSubscriberTriggeringFired(); } catch (RemoteException e) { - Slog.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); + Log.w(TAG, "Failed to inform statsd of periodic alarm firing.", e); } } // AlarmManager releases its own wakelock here. @@ -381,16 +383,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { return; } - Slog.i(TAG, "StatsCompanionService noticed a shutdown."); + Log.i(TAG, "StatsCompanionService noticed a shutdown."); synchronized (sStatsdLock) { if (sStatsd == null) { - Slog.w(TAG, "Could not access statsd to inform it of a shutdown event."); + Log.w(TAG, "Could not access statsd to inform it of a shutdown event."); return; } try { sStatsd.informDeviceShutdown(); } catch (Exception e) { - Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e); + Log.w(TAG, "Failed to inform statsd of a shutdown event.", e); } } } @@ -398,8 +400,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAnomalyAlarm(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); - if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs); + StatsCompanion.enforceStatsdCallingUid(); + if (DEBUG) Log.d(TAG, "Setting anomaly alarm for " + timestampMs); final long callingToken = Binder.clearCallingIdentity(); try { // using ELAPSED_REALTIME, not ELAPSED_REALTIME_WAKEUP, so if device is asleep, will @@ -414,8 +416,8 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAnomalyAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); - if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm"); + StatsCompanion.enforceStatsdCallingUid(); + if (DEBUG) Log.d(TAG, "Cancelling anomaly alarm"); final long callingToken = Binder.clearCallingIdentity(); try { mAlarmManager.cancel(mAnomalyAlarmListener); @@ -426,9 +428,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setAlarmForSubscriberTriggering(long timestampMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, + Log.d(TAG, "Setting periodic alarm in about " + (timestampMs - SystemClock.elapsedRealtime())); } @@ -445,9 +447,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelAlarmForSubscriberTriggering() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling periodic alarm"); + Log.d(TAG, "Cancelling periodic alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -459,9 +461,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void setPullingAlarm(long nextPullTimeMs) { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Setting pulling alarm in about " + Log.d(TAG, "Setting pulling alarm in about " + (nextPullTimeMs - SystemClock.elapsedRealtime())); } final long callingToken = Binder.clearCallingIdentity(); @@ -477,9 +479,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void cancelPullingAlarm() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "Cancelling pulling alarm"); + Log.d(TAG, "Cancelling pulling alarm"); } final long callingToken = Binder.clearCallingIdentity(); try { @@ -491,31 +493,36 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { @Override // Binder call public void statsdReady() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); if (DEBUG) { - Slog.d(TAG, "learned that statsdReady"); + Log.d(TAG, "learned that statsdReady"); } sayHiToStatsd(); // tell statsd that we're ready too and link to it mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED) - .addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND), + .addFlags(FLAG_RECEIVER_INCLUDE_BACKGROUND), UserHandle.SYSTEM, android.Manifest.permission.DUMP); } @Override public void triggerUidSnapshot() { - StatsCompanion.enforceStatsCompanionPermission(mContext); + StatsCompanion.enforceStatsdCallingUid(); synchronized (sStatsdLock) { final long token = Binder.clearCallingIdentity(); try { informAllUidsLocked(mContext); } catch (RemoteException e) { - Slog.e(TAG, "Failed to trigger uid snapshot.", e); + Log.e(TAG, "Failed to trigger uid snapshot.", e); } finally { restoreCallingIdentity(token); } } } + @Override // Binder call + public boolean checkPermission(String permission, int pid, int uid) { + StatsCompanion.enforceStatsdCallingUid(); + return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; + } // Statsd related code @@ -535,7 +542,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { * Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */ void systemReady() { - if (DEBUG) Slog.d(TAG, "Learned that systemReady"); + if (DEBUG) Log.d(TAG, "Learned that systemReady"); sayHiToStatsd(); } @@ -550,27 +557,27 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void sayHiToStatsd() { synchronized (sStatsdLock) { if (sStatsd != null) { - Slog.e(TAG, "Trying to fetch statsd, but it was already fetched", + Log.e(TAG, "Trying to fetch statsd, but it was already fetched", new IllegalStateException( "sStatsd is not null when being fetched")); return; } sStatsd = fetchStatsdService(); if (sStatsd == null) { - Slog.i(TAG, + Log.i(TAG, "Could not yet find statsd to tell it that StatsCompanion is " + "alive."); return; } mStatsManagerService.statsdReady(sStatsd); - if (DEBUG) Slog.d(TAG, "Saying hi to statsd"); + if (DEBUG) Log.d(TAG, "Saying hi to statsd"); try { sStatsd.statsCompanionReady(); // If the statsCompanionReady two-way binder call returns, link to statsd. try { sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0); } catch (RemoteException e) { - Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); + Log.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e); forgetEverythingLocked(); } // Setup broadcast receiver for updates. @@ -600,9 +607,9 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { } finally { restoreCallingIdentity(token); } - Slog.i(TAG, "Told statsd that StatsCompanionService is alive."); + Log.i(TAG, "Told statsd that StatsCompanionService is alive."); } catch (RemoteException e) { - Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e); + Log.e(TAG, "Failed to inform statsd that statscompanion is ready", e); forgetEverythingLocked(); } } @@ -611,7 +618,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private class StatsdDeathRecipient implements IBinder.DeathRecipient { @Override public void binderDied() { - Slog.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); + Log.i(TAG, "Statsd is dead - erase all my knowledge, except pullers"); synchronized (sStatsdLock) { long now = SystemClock.elapsedRealtime(); for (Long timeMillis : mDeathTimeMillis) { @@ -651,22 +658,15 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { cancelAnomalyAlarm(); cancelPullingAlarm(); - BinderCallsStatsService.Internal binderStats = - LocalServices.getService(BinderCallsStatsService.Internal.class); - if (binderStats != null) { - binderStats.reset(); - } - - LooperStats looperStats = LocalServices.getService(LooperStats.class); - if (looperStats != null) { - looperStats.reset(); - } mStatsManagerService.statsdNotReady(); } @Override protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return; + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + return; + } synchronized (sStatsdLock) { writer.println( diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java index 04d8b006f51d..4e4bc40b727f 100644 --- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java +++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java @@ -30,7 +30,7 @@ import android.os.IStatsd; import android.os.Process; import android.os.RemoteException; import android.util.ArrayMap; -import android.util.Slog; +import android.util.Log; import com.android.internal.annotations.GuardedBy; @@ -171,8 +171,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs, int[] additiveFields, IPullAtomCallback pullerCallback) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback); @@ -187,11 +187,12 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.registerPullAtomCallback( callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to register puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -199,8 +200,8 @@ public class StatsManagerService extends IStatsManagerService.Stub { @Override public void unregisterPullAtomCallback(int atomTag) { + enforceRegisterStatsPullAtomPermission(); int callingUid = Binder.getCallingUid(); - final long token = Binder.clearCallingIdentity(); PullerKey key = new PullerKey(callingUid, atomTag); // Always remove the puller from StatsManagerService even if statsd is down. When statsd @@ -214,10 +215,11 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } + final long token = Binder.clearCallingIdentity(); try { statsd.unregisterPullAtomCallback(callingUid, atomTag); } catch (RemoteException e) { - Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); + Log.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag); } finally { Binder.restoreCallingIdentity(token); } @@ -241,7 +243,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.setDataFetchOperation(configId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setDataFetchOperation with statsd"); + Log.e(TAG, "Failed to setDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -262,7 +264,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeDataFetchOperation(configId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeDataFetchOperation with statsd"); + Log.e(TAG, "Failed to removeDataFetchOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -285,7 +287,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.setActiveConfigsChangedOperation(pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -306,7 +308,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.removeActiveConfigsChangedOperation(callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); + Log.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -334,7 +336,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { configId, subscriberId, pir, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to setBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to setBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -360,7 +362,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); + Log.e(TAG, "Failed to unsetBroadcastSubscriber with statsd"); } finally { Binder.restoreCallingIdentity(token); } @@ -376,7 +378,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getRegisteredExperimentIds(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); + Log.e(TAG, "Failed to getRegisteredExperimentIds with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -394,7 +396,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getMetadata(); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getMetadata with statsd"); + Log.e(TAG, "Failed to getMetadata with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -413,7 +415,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return statsd.getData(key, callingUid); } } catch (RemoteException e) { - Slog.e(TAG, "Failed to getData with statsd"); + Log.e(TAG, "Failed to getData with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -434,7 +436,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to addConfiguration with statsd"); + Log.e(TAG, "Failed to addConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -455,7 +457,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { return; } } catch (RemoteException e) { - Slog.e(TAG, "Failed to removeConfiguration with statsd"); + Log.e(TAG, "Failed to removeConfiguration with statsd"); throw new IllegalStateException(e.getMessage(), e); } finally { Binder.restoreCallingIdentity(token); @@ -502,6 +504,13 @@ public class StatsManagerService extends IStatsManagerService.Stub { } } + private void enforceRegisterStatsPullAtomPermission() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.REGISTER_STATS_PULL_ATOM, + "Need REGISTER_STATS_PULL_ATOM permission."); + } + + /** * Clients should call this if blocking until statsd to be ready is desired * @@ -513,7 +522,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { try { mLock.wait(STATSD_TIMEOUT_MILLIS); } catch (InterruptedException e) { - Slog.e(TAG, "wait for statsd interrupted"); + Log.e(TAG, "wait for statsd interrupted"); } } return mStatsd; @@ -569,7 +578,7 @@ public class StatsManagerService extends IStatsManagerService.Stub { registerAllActiveConfigsChangedOperations(statsd); registerAllBroadcastSubscribers(statsd); } catch (RemoteException e) { - Slog.e(TAG, "StatsManager failed to (re-)register data with statsd"); + Log.e(TAG, "StatsManager failed to (re-)register data with statsd"); } finally { Binder.restoreCallingIdentity(token); } diff --git a/apex/statsd/testing/Android.bp b/apex/statsd/testing/Android.bp index 22e73015ba39..a9cd0ccb53e8 100644 --- a/apex/statsd/testing/Android.bp +++ b/apex/statsd/testing/Android.bp @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -apex { +apex_test { name: "test_com.android.os.statsd", visibility: [ "//system/apex/tests", diff --git a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp index e4ab823f345a..22daa8eb6b3e 100644 --- a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp +++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp @@ -46,15 +46,15 @@ static void init() { } } -static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* data, - void* /*cookie*/) { +static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data, + void* /*cookie*/) { sNumPulls++; sleep_for(std::chrono::milliseconds(sLatencyMillis)); for (int i = 0; i < sAtomsPerPull; i++) { - stats_event* event = add_stats_event_to_pull_data(data); - stats_event_set_atom_id(event, atomTag); - stats_event_write_int64(event, (int64_t) sNumPulls); - stats_event_build(event); + AStatsEvent* event = AStatsEventList_addStatsEvent(data); + AStatsEvent_setAtomId(event, atomTag); + AStatsEvent_writeInt64(event, (int64_t) sNumPulls); + AStatsEvent_build(event); } return sPullReturnVal; } @@ -71,11 +71,12 @@ Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_registerStatsPull sLatencyMillis = latencyMillis; sAtomsPerPull = atomsPerPull; sNumPulls = 0; - pull_atom_metadata metadata = {.cool_down_ns = coolDownNs, - .timeout_ns = timeoutNs, - .additive_fields = nullptr, - .additive_fields_size = 0}; - register_stats_pull_atom_callback(sAtomTag, &pullAtomCallback, &metadata, nullptr); + AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain(); + AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, coolDownNs); + AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, timeoutNs); + + AStatsManager_registerPullAtomCallback(sAtomTag, &pullAtomCallback, metadata, nullptr); + AStatsManager_PullAtomMetadata_release(metadata); } extern "C" @@ -83,6 +84,6 @@ JNIEXPORT void JNICALL Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller( JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/) { - unregister_stats_pull_atom_callback(sAtomTag); + AStatsManager_unregisterPullAtomCallback(sAtomTag); } -} // namespace
\ No newline at end of file +} // namespace diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java index dbd636d2e95c..e119b4c47604 100644 --- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java +++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java @@ -71,7 +71,6 @@ public class LibStatsPullTests { */ @Before public void setup() { -// Debug.waitForDebugger(); mContext = InstrumentationRegistry.getTargetContext(); assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull(); sPullReturnValue = StatsManager.PULL_SUCCESS; |