From d18a2cebb1bf06e4c081eb021941c2d1a5b73604 Mon Sep 17 00:00:00 2001 From: Zim Date: Fri, 5 Feb 2021 04:27:47 +0000 Subject: [PATCH] Get ANR delay from the MediaProvider StorageManagerService registers an AnrController with the ActivityManager so that when an app is about to ANR, it receives a callback to get an ANR delay. This request is forwarded to the MediaProvider that checks if the uid is blocked on a transcoding job and if so returns an appropriate delay Bug: 170486601 Test: Manual CTS-Coverage-Bug: 179658703 Change-Id: I9787d8127ed9757a23be7337bd4454a488d44141 --- core/api/system-current.txt | 1 + .../storage/ExternalStorageService.java | 48 ++++++++++ .../storage/IExternalStorageService.aidl | 1 + .../android/server/StorageManagerService.java | 9 +- .../storage/StorageSessionController.java | 23 +++++ .../server/storage/StorageUserConnection.java | 89 ++++++++++++++++--- 6 files changed, 155 insertions(+), 16 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 086325cfbfb84..31ccf1e88d534 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10385,6 +10385,7 @@ package android.service.storage { method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onEndSession(@NonNull String) throws java.io.IOException; method public void onFreeCache(@NonNull java.util.UUID, long) throws java.io.IOException; + method public long onGetAnrDelayMillis(@NonNull String, int); method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException; method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException; field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2 diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index a750b689ee02a..87add57f383d5 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -95,6 +95,21 @@ public abstract class ExternalStorageService extends Service { public static final String EXTRA_ERROR = "android.service.storage.extra.error"; + /** + * {@link Bundle} key for a package name {@link String} value. + * + * {@hide} + */ + public static final String EXTRA_PACKAGE_NAME = "android.service.storage.extra.package_name"; + + /** + * {@link Bundle} key for a {@link Long} value. + * + * {@hide} + */ + public static final String EXTRA_ANR_TIMEOUT_MS = + "android.service.storage.extra.anr_timeout_ms"; + /** @hide */ @IntDef(flag = true, prefix = {"FLAG_SESSION_"}, value = {FLAG_SESSION_TYPE_FUSE, FLAG_SESSION_ATTRIBUTE_INDEXABLE}) @@ -162,6 +177,15 @@ public abstract class ExternalStorageService extends Service { throw new UnsupportedOperationException("onFreeCacheRequested not implemented"); } + /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long onGetAnrDelayMillis(@NonNull String packageName, int uid) { + throw new UnsupportedOperationException("onGetAnrDelayMillis not implemented"); + } + @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { @@ -222,6 +246,19 @@ public abstract class ExternalStorageService extends Service { }); } + @Override + public void getAnrDelayMillis(String packageName, int uid, RemoteCallback callback) + throws RemoteException { + mHandler.post(() -> { + try { + long timeoutMs = onGetAnrDelayMillis(packageName, uid); + sendTimeoutResult(packageName, timeoutMs, null /* throwable */, callback); + } catch (Throwable t) { + sendTimeoutResult(packageName, 0 /* timeoutMs */, t, callback); + } + }); + } + private void sendResult(String sessionId, Throwable throwable, RemoteCallback callback) { Bundle bundle = new Bundle(); bundle.putString(EXTRA_SESSION_ID, sessionId); @@ -230,5 +267,16 @@ public abstract class ExternalStorageService extends Service { } callback.sendResult(bundle); } + + private void sendTimeoutResult(String packageName, long timeoutMs, Throwable throwable, + RemoteCallback callback) { + Bundle bundle = new Bundle(); + bundle.putString(EXTRA_PACKAGE_NAME, packageName); + bundle.putLong(EXTRA_ANR_TIMEOUT_MS, timeoutMs); + if (throwable != null) { + bundle.putParcelable(EXTRA_ERROR, new ParcelableException(throwable)); + } + callback.sendResult(bundle); + } } } diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl index d06671b3fb9f0..2e0bd86c3d7dc 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -32,4 +32,5 @@ oneway interface IExternalStorageService in RemoteCallback callback); void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes, in RemoteCallback callback); + void getAnrDelayMillis(String packageName, int uid, in RemoteCallback callback); } \ No newline at end of file diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 1ad0176d3c5bf..2f9819997257a 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -939,9 +939,12 @@ class StorageManagerService extends IStorageManager.Stub if (transcodeEnabled) { LocalServices.getService(ActivityManagerInternal.class) .registerAnrController((packageName, uid) -> { - // TODO: Retrieve delay from ExternalStorageService that can check - // transcoding status - return SystemProperties.getInt("sys.fuse.transcode_anr_delay_ms", 0); + try { + return mStorageSessionController.getAnrDelayMillis(packageName, uid); + } catch (ExternalStorageServiceException e) { + Log.e(TAG, "Failed to get ANR delay for " + packageName, e); + return 0; + } }); } } diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index eb4a0501a9533..0087c0c298536 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -157,6 +157,29 @@ public final class StorageSessionController { } } + /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long getAnrDelayMillis(String packageName, int uid) + throws ExternalStorageServiceException { + synchronized (mLock) { + int size = mConnections.size(); + for (int i = 0; i < size; i++) { + int key = mConnections.keyAt(i); + StorageUserConnection connection = mConnections.get(key); + if (connection != null) { + long delay = connection.getAnrDelayMillis(packageName, uid); + if (delay > 0) { + return delay; + } + } + } + } + return 0; + } + /** * Removes and returns the {@link StorageUserConnection} for {@code vol}. * diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index 13cceeed84e64..709d558ea0bcd 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -16,6 +16,7 @@ package com.android.server.storage; +import static android.service.storage.ExternalStorageService.EXTRA_ANR_TIMEOUT_MS; import static android.service.storage.ExternalStorageService.EXTRA_ERROR; import static android.service.storage.ExternalStorageService.FLAG_SESSION_ATTRIBUTE_INDEXABLE; import static android.service.storage.ExternalStorageService.FLAG_SESSION_TYPE_FUSE; @@ -142,6 +143,24 @@ public final class StorageUserConnection { } } + /** + * Called when {@code packageName} is about to ANR + * + * @return ANR dialog delay in milliseconds + */ + public long getAnrDelayMillis(String packageName, int uid) + throws ExternalStorageServiceException { + synchronized (mSessionsLock) { + for (String sessionId : mSessions.keySet()) { + long delay = mActiveConnection.getAnrDelayMillis(packageName, uid); + if (delay > 0) { + return delay; + } + } + } + return 0; + } + /** * Removes a session without ending it or waiting for exit. * @@ -234,6 +253,9 @@ public final class StorageUserConnection { @GuardedBy("mLock") private final ArrayList> mOutstandingOps = new ArrayList<>(); + @GuardedBy("mLock") + private final ArrayList> mOutstandingTimeoutOps = new ArrayList<>(); + @Override public void close() { ServiceConnection oldConnection = null; @@ -250,6 +272,9 @@ public final class StorageUserConnection { for (CompletableFuture op : mOutstandingOps) { op.cancel(true); } + for (CompletableFuture op : mOutstandingTimeoutOps) { + op.cancel(true); + } mOutstandingOps.clear(); } @@ -264,27 +289,44 @@ public final class StorageUserConnection { } } - private void waitForAsync(AsyncStorageServiceCall asyncCall) throws Exception { - CompletableFuture serviceFuture = connectIfNeeded(); + private void waitForAsyncVoid(AsyncStorageServiceCall asyncCall) throws Exception { CompletableFuture opFuture = new CompletableFuture<>(); + RemoteCallback callback = new RemoteCallback(result -> setResult(result, opFuture)); + + waitForAsync(asyncCall, callback, opFuture, mOutstandingOps, + DEFAULT_REMOTE_TIMEOUT_SECONDS); + } + + private long waitForAsyncLong(AsyncStorageServiceCall asyncCall) throws Exception { + CompletableFuture opFuture = new CompletableFuture<>(); + RemoteCallback callback = + new RemoteCallback(result -> setTimeoutResult(result, opFuture)); + + return waitForAsync(asyncCall, callback, opFuture, mOutstandingTimeoutOps, + 1 /* timeoutSeconds */); + } + + private T waitForAsync(AsyncStorageServiceCall asyncCall, RemoteCallback callback, + CompletableFuture opFuture, ArrayList> outstandingOps, + long timeoutSeconds) throws Exception { + CompletableFuture serviceFuture = connectIfNeeded(); try { synchronized (mLock) { - mOutstandingOps.add(opFuture); + outstandingOps.add(opFuture); } - serviceFuture.thenCompose(service -> { + return serviceFuture.thenCompose(service -> { try { - asyncCall.run(service, - new RemoteCallback(result -> setResult(result, opFuture))); + asyncCall.run(service, callback); } catch (RemoteException e) { opFuture.completeExceptionally(e); } return opFuture; - }).get(DEFAULT_REMOTE_TIMEOUT_SECONDS, TimeUnit.SECONDS); + }).get(timeoutSeconds, TimeUnit.SECONDS); } finally { synchronized (mLock) { - mOutstandingOps.remove(opFuture); + outstandingOps.remove(opFuture); } } } @@ -292,9 +334,9 @@ public final class StorageUserConnection { public void startSession(Session session, ParcelFileDescriptor fd) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> service.startSession(session.sessionId, + waitForAsyncVoid((service, callback) -> service.startSession(session.sessionId, FLAG_SESSION_TYPE_FUSE | FLAG_SESSION_ATTRIBUTE_INDEXABLE, - fd, session.upperPath, session.lowerPath, callback)); + fd, session.upperPath, session.lowerPath, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to start session: " + session, e); } finally { @@ -308,7 +350,7 @@ public final class StorageUserConnection { public void endSession(Session session) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.endSession(session.sessionId, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to end session: " + session, e); @@ -319,7 +361,7 @@ public final class StorageUserConnection { public void notifyVolumeStateChanged(String sessionId, StorageVolume vol) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.notifyVolumeStateChanged(sessionId, vol, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to notify volume state changed " @@ -330,7 +372,7 @@ public final class StorageUserConnection { public void freeCache(String sessionId, String volumeUuid, long bytes) throws ExternalStorageServiceException { try { - waitForAsync((service, callback) -> + waitForAsyncVoid((service, callback) -> service.freeCache(sessionId, volumeUuid, bytes, callback)); } catch (Exception e) { throw new ExternalStorageServiceException("Failed to free " + bytes @@ -338,6 +380,27 @@ public final class StorageUserConnection { } } + public long getAnrDelayMillis(String packgeName, int uid) + throws ExternalStorageServiceException { + try { + return waitForAsyncLong((service, callback) -> + service.getAnrDelayMillis(packgeName, uid, callback)); + } catch (Exception e) { + throw new ExternalStorageServiceException("Failed to notify app not responding: " + + packgeName, e); + } + } + + private void setTimeoutResult(Bundle result, CompletableFuture future) { + ParcelableException ex = result.getParcelable(EXTRA_ERROR); + if (ex != null) { + future.completeExceptionally(ex); + } else { + long timeoutMs = result.getLong(EXTRA_ANR_TIMEOUT_MS); + future.complete(timeoutMs); + } + } + private void setResult(Bundle result, CompletableFuture future) { ParcelableException ex = result.getParcelable(EXTRA_ERROR); if (ex != null) {