diff options
4 files changed, 159 insertions, 14 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 15b13dc97554..ffb920b907ab 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -757,6 +757,15 @@ interface IActivityManager { void addStartInfoTimestamp(int key, long timestampNs, int userId); /** + * Reports view related timestamps to be added to the calling apps most + * recent {@link ApplicationStartInfo}. + * + * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start + * @param framePresentedTimeNs Clock monotonic time in nanoseconds of frame presented + */ + oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs); + + /** * Return a list of {@link ApplicationExitInfo} records. * * <p class="note"> Note: System stores these historical information in a ring buffer, older diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index beb1a5d4ee15..cb82278ca577 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -177,6 +177,7 @@ import android.graphics.Region; import android.graphics.RenderNode; import android.graphics.drawable.Drawable; import android.graphics.drawable.GradientDrawable; +import android.hardware.SyncFence; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.display.DisplayManagerGlobal; @@ -218,6 +219,7 @@ import android.util.proto.ProtoOutputStream; import android.view.InputDevice.InputSourceClass; import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl.Transaction; +import android.view.SurfaceControl.TransactionStats; import android.view.View.AttachInfo; import android.view.View.FocusDirection; import android.view.View.MeasureSpec; @@ -292,6 +294,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.Predicate; /** * The top of a view hierarchy, implementing the needed protocol between View @@ -1188,6 +1191,13 @@ public final class ViewRootImpl implements ViewParent, private String mFpsTraceName; private String mLargestViewTraceName; + private final boolean mAppStartInfoTimestampsFlagValue; + @GuardedBy("this") + private boolean mAppStartTimestampsSent = false; + private boolean mAppStartTrackingStarted = false; + private long mRenderThreadDrawStartTimeNs = -1; + private long mFirstFramePresentedTimeNs = -1; + private static boolean sToolkitSetFrameRateReadOnlyFlagValue; private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; @@ -1304,6 +1314,8 @@ public final class ViewRootImpl implements ViewParent, } else { mSensitiveContentProtectionService = null; } + + mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps(); } public static void addFirstDrawHandler(Runnable callback) { @@ -2576,6 +2588,12 @@ public final class ViewRootImpl implements ViewParent, notifySurfaceDestroyed(); } destroySurface(); + + // Reset so they can be sent again for warm starts. + mAppStartTimestampsSent = false; + mAppStartTrackingStarted = false; + mRenderThreadDrawStartTimeNs = -1; + mFirstFramePresentedTimeNs = -1; } } } @@ -4374,6 +4392,30 @@ public final class ViewRootImpl implements ViewParent, reportDrawFinished(t, seqId); } }); + + // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if + // {link mTransactionCompletedTimeNs} has already been set. + if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) { + mAppStartTrackingStarted = true; + Transaction transaction = new Transaction(); + transaction.addTransactionCompletedListener(mExecutor, + new Consumer<TransactionStats>() { + @Override + public void accept(TransactionStats transactionStats) { + SyncFence presentFence = transactionStats.getPresentFence(); + if (presentFence.awaitForever()) { + if (mFirstFramePresentedTimeNs == -1) { + // Only trigger once per {@link ViewRootImpl} instance. + mFirstFramePresentedTimeNs = presentFence.getSignalTime(); + maybeSendAppStartTimes(); + } + } + presentFence.close(); + } + }); + applyTransactionOnDraw(transaction); + } + if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); } @@ -4381,6 +4423,45 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroup.add(this, null /* runnable */); } + private void maybeSendAppStartTimes() { + synchronized (this) { + if (mAppStartTimestampsSent) { + // Don't send timestamps more than once. + return; + } + + // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not + // post to main thread and check if we have it there. + if (mRenderThreadDrawStartTimeNs != -1) { + sendAppStartTimesLocked(); + } else { + mHandler.post(new Runnable() { + @Override + public void run() { + synchronized (ViewRootImpl.this) { + if (mRenderThreadDrawStartTimeNs == -1) { + return; + } + sendAppStartTimesLocked(); + } + } + }); + } + } + } + + @GuardedBy("this") + private void sendAppStartTimesLocked() { + try { + ActivityManager.getService().reportStartInfoViewTimestamps( + mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs); + mAppStartTimestampsSent = true; + } catch (RemoteException e) { + // Ignore, timestamps may be lost. + if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e); + } + } + /** * Helper used to notify the service to block projection when a sensitive * view (the view displays sensitive content) is attached to the window. @@ -5567,7 +5648,13 @@ public final class ViewRootImpl implements ViewParent, registerCallbackForPendingTransactions(); } + long timeNs = SystemClock.uptimeNanos(); mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); + + // Only trigger once per {@link ViewRootImpl} instance. + if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) { + mRenderThreadDrawStartTimeNs = timeNs; + } } else { // If we get here with a disabled & requested hardware renderer, something went // wrong (an invalidate posted right before we destroyed the hardware surface diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e0ec17124318..d4d33c93db46 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -10201,6 +10201,19 @@ public class ActivityManagerService extends IActivityManager.Stub addStartInfoTimestampInternal(key, timestampNs, userId, callingUid); } + @Override + public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, + long framePresentedTimeNs) { + int callingUid = Binder.getCallingUid(); + int userId = UserHandle.getUserId(callingUid); + addStartInfoTimestampInternal( + ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME, + renderThreadDrawStartTimeNs, userId, callingUid); + addStartInfoTimestampInternal( + ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE, + framePresentedTimeNs, userId, callingUid); + } + private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) { mProcessList.getAppStartInfoTracker().addTimestampToStart( Settings.getPackageNameForUid(mContext, uid), diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index a8227fa8e38b..dc6e2fa39b65 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -1128,21 +1128,8 @@ public final class AppStartInfoTracker { // Records are sorted newest to oldest, grab record at index 0. ApplicationStartInfo startInfo = mInfos.get(0); - int startupState = startInfo.getStartupState(); - // If startup state is error then don't accept any further timestamps. - if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { - if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); - return; - } - - // If startup state is first frame drawn then only accept fully drawn timestamp. - if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN - && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { - if (DEBUG) { - Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully " - + "drawn, not accepting new timestamps."); - } + if (!isAddTimestampAllowed(startInfo, key, timestampNs)) { return; } @@ -1155,6 +1142,55 @@ public final class AppStartInfoTracker { } } + private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key, + long timestampNs) { + int startupState = startInfo.getStartupState(); + + // If startup state is error then don't accept any further timestamps. + if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { + if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); + return false; + } + + Map<Integer, Long> timestamps = startInfo.getStartupTimestamps(); + + if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) { + switch (key) { + case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN: + // Allowed, continue to confirm it's not already added. + break; + case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME: + Long firstFrameTimeNs = timestamps + .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); + if (firstFrameTimeNs == null) { + // This should never happen. State can't be first frame drawn if first + // frame timestamp was not provided. + return false; + } + + if (timestampNs > firstFrameTimeNs) { + // Initial renderthread frame has to occur before first frame. + return false; + } + + // Allowed, continue to confirm it's not already added. + break; + case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE: + // Allowed, continue to confirm it's not already added. + break; + default: + return false; + } + } + + if (timestamps.get(key) != null) { + // Timestamp should not occur more than once for a given start. + return false; + } + + return true; + } + @GuardedBy("mLock") void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) { if (mMonitoringModeEnabled) { |