diff options
13 files changed, 157 insertions, 26 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 19a1d7af5382..45df0d9a8a9f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1294,8 +1294,11 @@ public final class ActivityThread extends ClientTransactionHandler } @Override - public void scheduleCrash(String msg, int typeId) { - sendMessage(H.SCHEDULE_CRASH, msg, typeId); + public void scheduleCrash(String msg, int typeId, @Nullable Bundle extras) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = msg; + args.arg2 = extras; + sendMessage(H.SCHEDULE_CRASH, args, typeId); } public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken, @@ -1925,11 +1928,11 @@ public final class ActivityThread extends ClientTransactionHandler } } - private void throwRemoteServiceException(String message, int typeId) { + private void throwRemoteServiceException(String message, int typeId, @Nullable Bundle extras) { // Use a switch to ensure all the type IDs are unique. switch (typeId) { case ForegroundServiceDidNotStartInTimeException.TYPE_ID: - throw new ForegroundServiceDidNotStartInTimeException(message); + throw generateForegroundServiceDidNotStartInTimeException(message, extras); case CannotDeliverBroadcastException.TYPE_ID: throw new CannotDeliverBroadcastException(message); @@ -1952,6 +1955,15 @@ public final class ActivityThread extends ClientTransactionHandler } } + private ForegroundServiceDidNotStartInTimeException + generateForegroundServiceDidNotStartInTimeException(String message, Bundle extras) { + final String serviceClassName = + ForegroundServiceDidNotStartInTimeException.getServiceClassNameFromExtras(extras); + final Exception inner = (serviceClassName == null) ? null + : Service.getStartForegroundServiceStackTrace(serviceClassName); + throw new ForegroundServiceDidNotStartInTimeException(message, inner); + } + class H extends Handler { public static final int BIND_APPLICATION = 110; @UnsupportedAppUsage @@ -2169,9 +2181,14 @@ public final class ActivityThread extends ClientTransactionHandler handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; - case SCHEDULE_CRASH: - throwRemoteServiceException((String) msg.obj, msg.arg1); + case SCHEDULE_CRASH: { + SomeArgs args = (SomeArgs) msg.obj; + String message = (String) args.arg1; + Bundle extras = (Bundle) args.arg2; + args.recycle(); + throwRemoteServiceException(message, msg.arg1, extras); break; + } case DUMP_HEAP: handleDumpHeap((DumpHeapData) msg.obj); break; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index e76c1398b04a..db3c7d9bcb02 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1862,6 +1862,14 @@ class ContextImpl extends Context { "Not allowed to start service " + service + ": " + cn.getClassName()); } } + // If we started a foreground service in the same package, remember the stack trace. + if (cn != null && requireForeground) { + if (cn.getPackageName().equals(getOpPackageName())) { + Service.setStartForegroundServiceStackTrace(cn.getClassName(), + new StackTrace("Last startServiceCommon() call for this service was " + + "made here")); + } + } return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 4912703bc2ce..64a9c441c57f 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -321,6 +321,8 @@ interface IActivityManager { boolean isTopActivityImmersive(); void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId, in String message, boolean force, int exceptionTypeId); + void crashApplicationWithTypeWithExtras(int uid, int initialPid, in String packageName, + int userId, in String message, boolean force, int exceptionTypeId, in Bundle extras); /** @deprecated -- use getProviderMimeTypeAsync */ @UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives = "Use {@link android.content.ContentResolver#getType} public API instead.") diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index d6ff6d3dfc3a..448c3cf48e10 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -107,7 +107,7 @@ oneway interface IApplicationThread { void scheduleOnNewActivityOptions(IBinder token, in Bundle options); void scheduleSuicide(); void dispatchPackageBroadcast(int cmd, in String[] packages); - void scheduleCrash(in String msg, int typeId); + void scheduleCrash(in String msg, int typeId, in Bundle extras); void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, in String path, in ParcelFileDescriptor fd, in RemoteCallback finishCallback); void dumpActivity(in ParcelFileDescriptor fd, IBinder servicetoken, in String prefix, diff --git a/core/java/android/app/RemoteServiceException.java b/core/java/android/app/RemoteServiceException.java index 1038530d92d3..e220627706f9 100644 --- a/core/java/android/app/RemoteServiceException.java +++ b/core/java/android/app/RemoteServiceException.java @@ -16,6 +16,10 @@ package android.app; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.ComponentName; +import android.os.Bundle; import android.util.AndroidRuntimeException; /** @@ -33,6 +37,10 @@ public class RemoteServiceException extends AndroidRuntimeException { super(msg); } + public RemoteServiceException(String msg, Throwable cause) { + super(msg, cause); + } + /** * Exception used to crash an app process when it didn't call {@link Service#startForeground} * in time after the service was started with @@ -44,8 +52,21 @@ public class RemoteServiceException extends AndroidRuntimeException { /** The type ID passed to {@link IApplicationThread#scheduleCrash}. */ public static final int TYPE_ID = 1; - public ForegroundServiceDidNotStartInTimeException(String msg) { - super(msg); + private static final String KEY_SERVICE_CLASS_NAME = "serviceclassname"; + + public ForegroundServiceDidNotStartInTimeException(String msg, Throwable cause) { + super(msg, cause); + } + + public static Bundle createExtrasForService(@NonNull ComponentName service) { + Bundle b = new Bundle(); + b.putString(KEY_SERVICE_CLASS_NAME, service.getClassName()); + return b; + } + + @Nullable + public static String getServiceClassNameFromExtras(@Nullable Bundle extras) { + return (extras == null) ? null : extras.getString(KEY_SERVICE_CLASS_NAME); } } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 336387204410..5a5ccb5d8987 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -33,9 +33,12 @@ import android.content.res.Configuration; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Log; import android.view.contentcapture.ContentCaptureManager; +import com.android.internal.annotations.GuardedBy; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -733,6 +736,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, FOREGROUND_SERVICE_TYPE_MANIFEST); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -786,6 +790,7 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mActivityManager.setServiceForeground( new ComponentName(this, mClassName), mToken, id, notification, 0, foregroundServiceType); + clearStartForegroundServiceStackTrace(); } catch (RemoteException ex) { } } @@ -941,4 +946,34 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private IActivityManager mActivityManager = null; @UnsupportedAppUsage private boolean mStartCompatibility = false; + + /** + * This keeps track of the stacktrace where Context.startForegroundService() was called + * for each service class. We use that when we crash the app for not calling + * {@link #startForeground} in time, in {@link ActivityThread#throwRemoteServiceException}. + */ + @GuardedBy("sStartForegroundServiceStackTraces") + private static final ArrayMap<String, StackTrace> sStartForegroundServiceStackTraces = + new ArrayMap<>(); + + /** @hide */ + public static void setStartForegroundServiceStackTrace( + @NonNull String className, @NonNull StackTrace stacktrace) { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.put(className, stacktrace); + } + } + + private void clearStartForegroundServiceStackTrace() { + synchronized (sStartForegroundServiceStackTraces) { + sStartForegroundServiceStackTraces.remove(this.getClassName()); + } + } + + /** @hide */ + public static StackTrace getStartForegroundServiceStackTrace(@NonNull String className) { + synchronized (sStartForegroundServiceStackTraces) { + return sStartForegroundServiceStackTraces.get(className); + } + } } diff --git a/core/java/android/app/StackTrace.java b/core/java/android/app/StackTrace.java new file mode 100644 index 000000000000..ec058f88118b --- /dev/null +++ b/core/java/android/app/StackTrace.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +/** + * An Exception subclass that's used only for logging stacktraces. + * @hide + */ +public class StackTrace extends Exception { + public StackTrace(String message) { + super(message); + } +} diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java index df0c64c810c6..9f48c06da385 100644 --- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java +++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java @@ -532,7 +532,7 @@ public class TransactionParcelTests { } @Override - public void scheduleCrash(String s, int i) throws RemoteException { + public void scheduleCrash(String s, int i, Bundle extras) throws RemoteException { } @Override diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index df2ae987f8cb..9a371f3b0b31 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -157,6 +157,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.procstats.ServiceState; import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastPrintWriter; @@ -4209,9 +4210,12 @@ public final class ActiveServices { if (r.app != null) { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG); - msg.obj = r.app; - msg.getData().putCharSequence( - ActivityManagerService.SERVICE_RECORD_KEY, r.toString()); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = r.app; + args.arg2 = r.toString(); + args.arg3 = r.getComponentName(); + + msg.obj = args; mAm.mHandler.sendMessage(msg); } } @@ -5272,11 +5276,14 @@ public final class ActiveServices { } } - void serviceForegroundCrash(ProcessRecord app, CharSequence serviceRecord) { - mAm.crashApplicationWithType(app.uid, app.getPid(), app.info.packageName, app.userId, + void serviceForegroundCrash(ProcessRecord app, String serviceRecord, + ComponentName service) { + mAm.crashApplicationWithTypeWithExtras( + app.uid, app.getPid(), app.info.packageName, app.userId, "Context.startForegroundService() did not then call Service.startForeground(): " + serviceRecord, false /*force*/, - ForegroundServiceDidNotStartInTimeException.TYPE_ID); + ForegroundServiceDidNotStartInTimeException.TYPE_ID, + ForegroundServiceDidNotStartInTimeException.createExtrasForService(service)); } void scheduleServiceTimeoutLocked(ProcessRecord proc) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index dde1ed9cdca9..53b1608841b4 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -338,6 +338,7 @@ import com.android.internal.os.BinderTransactionNameResolver; import com.android.internal.os.ByteTransferPipe; import com.android.internal.os.IResultReceiver; import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.SomeArgs; import com.android.internal.os.TransferPipe; import com.android.internal.os.Zygote; import com.android.internal.policy.AttributeCache; @@ -1472,8 +1473,6 @@ public class ActivityManagerService extends IActivityManager.Stub static final int FIRST_BROADCAST_QUEUE_MSG = 200; - static final String SERVICE_RECORD_KEY = "servicerecord"; - /** * Flag whether the current user is a "monkey", i.e. whether * the UI is driven by a UI automation tool. @@ -1652,8 +1651,12 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.serviceForegroundTimeout((ServiceRecord) msg.obj); } break; case SERVICE_FOREGROUND_CRASH_MSG: { - mServices.serviceForegroundCrash((ProcessRecord) msg.obj, - msg.getData().getCharSequence(SERVICE_RECORD_KEY)); + SomeArgs args = (SomeArgs) msg.obj; + mServices.serviceForegroundCrash( + (ProcessRecord) args.arg1, + (String) args.arg2, + (ComponentName) args.arg3); + args.recycle(); } break; case UPDATE_TIME_ZONE: { synchronized (mProcLock) { @@ -3001,6 +3004,14 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void crashApplicationWithType(int uid, int initialPid, String packageName, int userId, String message, boolean force, int exceptionTypeId) { + crashApplicationWithTypeWithExtras(uid, initialPid, packageName, userId, message, + force, exceptionTypeId, null); + } + + @Override + public void crashApplicationWithTypeWithExtras(int uid, int initialPid, String packageName, + int userId, String message, boolean force, int exceptionTypeId, + @Nullable Bundle extras) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { String msg = "Permission Denial: crashApplication() from pid=" @@ -3013,7 +3024,7 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized(this) { mAppErrors.scheduleAppCrashLocked(uid, initialPid, packageName, userId, - message, force, exceptionTypeId); + message, force, exceptionTypeId, extras); } } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index bcb42bb38495..0bf0fe2be246 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -27,6 +27,7 @@ import static com.android.server.am.ActivityManagerService.MY_PID; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AnrController; @@ -40,6 +41,7 @@ import android.content.pm.VersionedPackage; import android.net.Uri; import android.os.Binder; import android.os.Build; +import android.os.Bundle; import android.os.Message; import android.os.Process; import android.os.SystemClock; @@ -489,7 +491,7 @@ class AppErrors { * @param message */ void scheduleAppCrashLocked(int uid, int initialPid, String packageName, int userId, - String message, boolean force, int exceptionTypeId) { + String message, boolean force, int exceptionTypeId, @Nullable Bundle extras) { ProcessRecord proc = null; // Figure out which process to kill. We don't trust that initialPid @@ -521,7 +523,7 @@ class AppErrors { return; } - proc.scheduleCrashLocked(message, exceptionTypeId); + proc.scheduleCrashLocked(message, exceptionTypeId, extras); if (force) { // If the app is responsive, the scheduled crash will happen as expected // and then the delayed summary kill will be a no-op. diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index 487101b7eac4..2da41070a6f4 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -611,7 +611,7 @@ public final class BroadcastQueue { Slog.w(TAG, "Can't deliver broadcast to " + app.processName + " (pid " + app.getPid() + "). Crashing it."); app.scheduleCrashLocked("can't deliver broadcast", - CannotDeliverBroadcastException.TYPE_ID); + CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null); } throw ex; } diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 14ba7167f529..b9d7917ed38d 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -31,6 +31,7 @@ import android.content.pm.ProcessInfo; import android.content.pm.VersionedPackage; import android.content.res.CompatibilityInfo; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.os.Process; import android.os.RemoteException; @@ -958,7 +959,7 @@ class ProcessRecord implements WindowProcessListener { * of its subclasses. */ @GuardedBy("mService") - void scheduleCrashLocked(String message, int exceptionTypeId) { + void scheduleCrashLocked(String message, int exceptionTypeId, @Nullable Bundle extras) { // Checking killedbyAm should keep it from showing the crash dialog if the process // was already dead for a good / normal reason. if (!mKilledByAm) { @@ -969,7 +970,7 @@ class ProcessRecord implements WindowProcessListener { } final long ident = Binder.clearCallingIdentity(); try { - mThread.scheduleCrash(message, exceptionTypeId); + mThread.scheduleCrash(message, exceptionTypeId, extras); } catch (RemoteException e) { // If it's already dead our work is done. If it's wedged just kill it. // We won't get the crash dialog or the error reporting. |