diff --git a/api/current.txt b/api/current.txt index 77d948c8b9dec..d8cbb9a7f3ab9 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3745,6 +3745,7 @@ package android.app { } public class ActivityOptions { + method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int); @@ -3752,8 +3753,11 @@ package android.app { method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair...); method public static android.app.ActivityOptions makeTaskLaunchBehind(); method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); + method public void requestUsageTimeReport(android.app.PendingIntent); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); + field public static final java.lang.String EXTRA_USAGE_REPORT_PACKAGES = "android.package"; + field public static final java.lang.String EXTRA_USAGE_REPORT_TIME = "android.time"; } public class AlarmManager { diff --git a/api/system-current.txt b/api/system-current.txt index 53cc7162be05a..1610869763764 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3837,6 +3837,7 @@ package android.app { } public class ActivityOptions { + method public static android.app.ActivityOptions makeBasic(); method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int); method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int); method public static android.app.ActivityOptions makeScaleUpAnimation(android.view.View, int, int, int, int); @@ -3844,8 +3845,11 @@ package android.app { method public static android.app.ActivityOptions makeSceneTransitionAnimation(android.app.Activity, android.util.Pair...); method public static android.app.ActivityOptions makeTaskLaunchBehind(); method public static android.app.ActivityOptions makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int); + method public void requestUsageTimeReport(android.app.PendingIntent); method public android.os.Bundle toBundle(); method public void update(android.app.ActivityOptions); + field public static final java.lang.String EXTRA_USAGE_REPORT_PACKAGES = "android.package"; + field public static final java.lang.String EXTRA_USAGE_REPORT_TIME = "android.time"; } public class AlarmManager { diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 8909b28787c26..9f23b436127c1 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -25,6 +25,7 @@ import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Pair; +import android.util.Slog; import android.view.View; import android.view.Window; @@ -38,6 +39,19 @@ import java.util.ArrayList; public class ActivityOptions { private static final String TAG = "ActivityOptions"; + /** + * A long in the extras delivered by {@link #requestUsageTimeReport} that contains + * the total time (in ms) the user spent in the app. + */ + public static final String EXTRA_USAGE_REPORT_TIME = "android.time"; + + /** + * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains + * detailed information about the time spent in each package associated with the app; + * each key is a package name, whose value is a long containing the time (in ms). + */ + public static final String EXTRA_USAGE_REPORT_PACKAGES = "android.package"; + /** * The package name that created the options. * @hide @@ -118,6 +132,8 @@ public class ActivityOptions { private static final String KEY_RESULT_CODE = "android:resultCode"; private static final String KEY_EXIT_COORDINATOR_INDEX = "android:exitCoordinatorIndex"; + private static final String KEY_USAGE_TIME_REPORT = "android:usageTimeReport"; + /** @hide */ public static final int ANIM_NONE = 0; /** @hide */ @@ -160,6 +176,7 @@ public class ActivityOptions { private Intent mResultData; private int mResultCode; private int mExitCoordinatorIndex; + private PendingIntent mUsageTimeReport; /** * Create an ActivityOptions specifying a custom animation to run when @@ -586,6 +603,15 @@ public class ActivityOptions { return opts; } + /** + * Create a basic ActivityOptions that has no special animation associated with it. + * Other options can still be set. + */ + public static ActivityOptions makeBasic() { + final ActivityOptions opts = new ActivityOptions(); + return opts; + } + /** @hide */ public boolean getLaunchTaskBehind() { return mAnimationType == ANIM_LAUNCH_TASK_BEHIND; @@ -597,6 +623,11 @@ public class ActivityOptions { /** @hide */ public ActivityOptions(Bundle opts) { mPackageName = opts.getString(KEY_PACKAGE_NAME); + try { + mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT); + } catch (RuntimeException e) { + Slog.w(TAG, e); + } mAnimationType = opts.getInt(KEY_ANIM_TYPE); switch (mAnimationType) { case ANIM_CUSTOM: @@ -729,6 +760,11 @@ public class ActivityOptions { /** @hide */ public Intent getResultData() { return mResultData; } + /** @hide */ + public PendingIntent getUsageTimeReport() { + return mUsageTimeReport; + } + /** @hide */ public static void abort(Bundle options) { if (options != null) { @@ -745,6 +781,7 @@ public class ActivityOptions { if (otherOptions.mPackageName != null) { mPackageName = otherOptions.mPackageName; } + mUsageTimeReport = otherOptions.mUsageTimeReport; mTransitionReceiver = null; mSharedElementNames = null; mIsReturning = false; @@ -828,6 +865,9 @@ public class ActivityOptions { b.putString(KEY_PACKAGE_NAME, mPackageName); } b.putInt(KEY_ANIM_TYPE, mAnimationType); + if (mUsageTimeReport != null) { + b.putParcelable(KEY_USAGE_TIME_REPORT, mUsageTimeReport); + } switch (mAnimationType) { case ANIM_CUSTOM: b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId); @@ -872,6 +912,34 @@ public class ActivityOptions { return b; } + /** + * Ask the the system track that time the user spends in the app being launched, and + * report it back once done. The report will be sent to the given receiver, with + * the extras {@link #EXTRA_USAGE_REPORT_TIME} and {@link #EXTRA_USAGE_REPORT_PACKAGES} + * filled in. + * + *

The time interval tracked is from launching this activity until the user leaves + * that activity's flow. They are considered to stay in the flow as long as + * new activities are being launched or returned to from the original flow, + * even if this crosses package or task boundaries. For example, if the originator + * starts an activity to view an image, and while there the user selects to share, + * which launches their email app in a new task, and they complete the share, the + * time during that entire operation will be included until they finally hit back from + * the original image viewer activity.

+ * + *

The user is considered to complete a flow once they switch to another + * activity that is not part of the tracked flow. This may happen, for example, by + * using the notification shade, launcher, or recents to launch or switch to another + * app. Simply going in to these navigation elements does not break the flow (although + * the launcher and recents stops time tracking of the session); it is the act of + * going somewhere else that completes the tracking.

+ * + * @param receiver A broadcast receiver that willl receive the report. + */ + public void requestUsageTimeReport(PendingIntent receiver) { + mUsageTimeReport = receiver; + } + /** * Return the filtered options only meant to be seen by the target activity itself * @hide diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 75170904a9b22..b111b36276aa0 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -425,6 +425,11 @@ public final class ActivityManagerService extends ActivityManagerNative */ private int mLastFocusedUserId; + /** + * If non-null, we are tracking the time the user spends in the currently focused app. + */ + private AppTimeTracker mCurAppTimeTracker; + /** * List of intents that were used to start the most recent tasks. */ @@ -1330,7 +1335,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 51; static final int DELETE_DUMPHEAP_MSG = 52; static final int FOREGROUND_PROFILE_CHANGED_MSG = 53; - static final int DISPATCH_UIDS_CHANGED = 54; + static final int DISPATCH_UIDS_CHANGED_MSG = 54; + static final int REPORT_TIME_TRACKER_MSG = 55; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -1563,7 +1569,7 @@ public final class ActivityManagerService extends ActivityManagerNative dispatchProcessDied(pid, uid); break; } - case DISPATCH_UIDS_CHANGED: { + case DISPATCH_UIDS_CHANGED_MSG: { dispatchUidsChanged(); } break; } @@ -1988,6 +1994,10 @@ public final class ActivityManagerService extends ActivityManagerNative case FOREGROUND_PROFILE_CHANGED_MSG: { dispatchForegroundProfileChanged(msg.arg1); } break; + case REPORT_TIME_TRACKER_MSG: { + AppTimeTracker tracker = (AppTimeTracker)msg.obj; + tracker.deliverResult(mContext); + } break; } } }; @@ -2573,6 +2583,27 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_FOCUS) Slog.d(TAG_FOCUS, "setFocusedActivityLocked: r=" + r); ActivityRecord last = mFocusedActivity; mFocusedActivity = r; + if (r.task.taskType != ActivityRecord.HOME_ACTIVITY_TYPE + && r.task.taskType != ActivityRecord.RECENTS_ACTIVITY_TYPE) { + if (mCurAppTimeTracker != r.appTimeTracker) { + // We are switching app tracking. Complete the current one. + if (mCurAppTimeTracker != null) { + mCurAppTimeTracker.stop(); + mHandler.obtainMessage(REPORT_TIME_TRACKER_MSG, + mCurAppTimeTracker).sendToTarget(); + mStackSupervisor.clearOtherAppTimeTrackers(r.appTimeTracker); + mCurAppTimeTracker = null; + } + if (r.appTimeTracker != null) { + mCurAppTimeTracker = r.appTimeTracker; + startTimeTrackingFocusedActivityLocked(); + } + } else { + startTimeTrackingFocusedActivityLocked(); + } + } else { + r.appTimeTracker = null; + } if (r.task != null && r.task.voiceInteractor != null) { startRunningVoiceLocked(r.task.voiceSession, r.info.applicationInfo.uid); } else { @@ -10132,14 +10163,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } + void startTimeTrackingFocusedActivityLocked() { + if (!mSleeping && mCurAppTimeTracker != null && mFocusedActivity != null) { + mCurAppTimeTracker.start(mFocusedActivity.packageName); + } + } + void updateSleepIfNeededLocked() { if (mSleeping && !shouldSleepLocked()) { mSleeping = false; + startTimeTrackingFocusedActivityLocked(); mTopProcessState = ActivityManager.PROCESS_STATE_TOP; mStackSupervisor.comeOutOfSleepIfNeededLocked(); updateOomAdjLocked(); } else if (!mSleeping && shouldSleepLocked()) { mSleeping = true; + if (mCurAppTimeTracker != null) { + mCurAppTimeTracker.stop(); + } mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING; mStackSupervisor.goingToSleepLocked(); updateOomAdjLocked(); @@ -13342,6 +13383,9 @@ public final class ActivityManagerService extends ActivityManagerNative + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); } } + if (mCurAppTimeTracker != null) { + mCurAppTimeTracker.dumpWithHeader(pw, " ", true); + } if (mMemWatchProcesses.getMap().size() > 0) { pw.println(" Mem watch processes:"); final ArrayMap>> procs @@ -18460,7 +18504,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mPendingUidChanges.size() == 0) { if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "*** Enqueueing dispatch uid changed!"); - mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED).sendToTarget(); + mUiHandler.obtainMessage(DISPATCH_UIDS_CHANGED_MSG).sendToTarget(); } final int NA = mAvailUidChanges.size(); if (NA > 0) { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index ca1fd6a46bbea..54dd491c7c4d8 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -20,6 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.*; import static com.android.server.am.TaskRecord.INVALID_TASK_ID; import android.app.ActivityManager.TaskDescription; +import android.app.PendingIntent; import android.os.PersistableBundle; import android.os.Trace; @@ -142,6 +143,7 @@ final class ActivityRecord { ArrayList newIntents; // any pending new intents for single-top mode ActivityOptions pendingOptions; // most recently given options ActivityOptions returningOptions; // options that are coming back via convertToTranslucent + AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity HashSet connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. ProcessRecord app; // if non-null, hosting application @@ -262,6 +264,9 @@ final class ActivityRecord { if (pendingOptions != null) { pw.print(prefix); pw.print("pendingOptions="); pw.println(pendingOptions); } + if (appTimeTracker != null) { + appTimeTracker.dumpWithHeader(pw, prefix, false); + } if (uriPermissions != null) { uriPermissions.dump(pw, prefix); } @@ -463,6 +468,10 @@ final class ActivityRecord { if (options != null) { pendingOptions = new ActivityOptions(options); mLaunchTaskBehind = pendingOptions.getLaunchTaskBehind(); + PendingIntent usageReport = pendingOptions.getUsageTimeReport(); + if (usageReport != null) { + appTimeTracker = new AppTimeTracker(usageReport); + } } // This starts out true, since the initial state of an activity diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 6574538c47073..a4c557f6a9e80 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1447,6 +1447,19 @@ final class ActivityStack { mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } + void clearOtherAppTimeTrackers(AppTimeTracker except) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if ( r.appTimeTracker != except) { + r.appTimeTracker = null; + } + } + } + } + /** * Called as activities below the top translucent activity are redrawn. When the last one is * redrawn notify the top activity by calling @@ -3622,7 +3635,7 @@ final class ActivityStack { } final void moveTaskToFrontLocked(TaskRecord tr, boolean noAnimation, Bundle options, - String reason) { + AppTimeTracker timeTracker, String reason) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr); final int numTasks = mTaskHistory.size(); @@ -3637,6 +3650,13 @@ final class ActivityStack { return; } + if (timeTracker != null) { + // The caller wants a time tracker associated with this task. + for (int i = tr.mActivities.size() - 1; i >= 0; i--) { + tr.mActivities.get(i).appTimeTracker = timeTracker; + } + } + // Shift all activities with this task up to the top // of the stack, keeping them in the same internal order. insertTaskAtTop(tr, null); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 39df9d1558fb1..01a3fe386cd50 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1567,6 +1567,12 @@ public final class ActivityStackSupervisor implements DisplayListener { outActivity[0] = r; } + if (r.appTimeTracker == null && sourceRecord != null) { + // If the caller didn't specify an explicit time tracker, we want to continue + // tracking under any it has. + r.appTimeTracker = sourceRecord.appTimeTracker; + } + final ActivityStack stack = mFocusedStack; if (voiceSession == null && (stack.mResumedActivity == null || stack.mResumedActivity.info.applicationInfo.uid != callingUid)) { @@ -1950,7 +1956,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } movedHome = true; targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation, - options, "bringingFoundTaskToFront"); + options, r.appTimeTracker, "bringingFoundTaskToFront"); movedToFront = true; if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) @@ -2192,7 +2198,7 @@ public final class ActivityStackSupervisor implements DisplayListener { final TaskRecord topTask = targetStack.topTask(); if (topTask != sourceTask) { targetStack.moveTaskToFrontLocked(sourceTask, noAnimation, options, - "sourceTaskToFront"); + r.appTimeTracker, "sourceTaskToFront"); } if (!addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { // In this case, we are adding the activity to an existing @@ -2239,14 +2245,15 @@ public final class ActivityStackSupervisor implements DisplayListener { + " in existing task " + r.task + " from source " + sourceRecord); } else if (inTask != null) { - // The calling is asking that the new activity be started in an explicit + // The caller is asking that the new activity be started in an explicit // task it has provided to us. if (isLockTaskModeViolation(inTask)) { Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } targetStack = inTask.stack; - targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, "inTaskToFront"); + targetStack.moveTaskToFrontLocked(inTask, noAnimation, options, r.appTimeTracker, + "inTaskToFront"); // Check whether we should actually launch the new activity in to the task, // or just reuse the current activity on top. @@ -2628,7 +2635,9 @@ public final class ActivityStackSupervisor implements DisplayListener { + task + " to front. Stack is null"); return; } - task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, reason); + task.stack.moveTaskToFrontLocked(task, false /* noAnimation */, options, + task.getTopActivity() == null ? null : task.getTopActivity().appTimeTracker, + reason); if (DEBUG_STACK) Slog.d(TAG_STACK, "findTaskToMoveToFront: moved to front of stack=" + task.stack); } @@ -3143,6 +3152,17 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + void clearOtherAppTimeTrackers(AppTimeTracker except) { + for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { + final ArrayList stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + final int topStackNdx = stacks.size() - 1; + for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = stacks.get(stackNdx); + stack.clearOtherAppTimeTrackers(except); + } + } + } + void scheduleDestroyAllActivities(ProcessRecord app, String reason) { for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ArrayList stacks = mActivityDisplays.valueAt(displayNdx).mStacks; diff --git a/services/core/java/com/android/server/am/AppTimeTracker.java b/services/core/java/com/android/server/am/AppTimeTracker.java new file mode 100644 index 0000000000000..bddd66f51fc51 --- /dev/null +++ b/services/core/java/com/android/server/am/AppTimeTracker.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2015 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.am; + +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.SystemClock; +import android.util.ArrayMap; +import android.util.MutableLong; +import android.util.TimeUtils; + +import java.io.PrintWriter; + +/** + * Tracks the time a user spent in an app. + */ +public class AppTimeTracker { + private final PendingIntent mReceiver; + + private long mTotalTime; + private final ArrayMap mPackageTimes = new ArrayMap<>(); + + private long mStartedTime; + private String mStartedPackage; + private MutableLong mStartedPackageTime; + + public AppTimeTracker(PendingIntent receiver) { + mReceiver = receiver; + } + + public void start(String packageName) { + long now = SystemClock.elapsedRealtime(); + if (mStartedTime == 0) { + mStartedTime = now; + } + if (!packageName.equals(mStartedPackage)) { + if (mStartedPackageTime != null) { + long elapsedTime = now - mStartedTime; + mStartedPackageTime.value += elapsedTime; + mTotalTime += elapsedTime; + } + mStartedPackage = packageName; + mStartedPackageTime = mPackageTimes.get(packageName); + if (mStartedPackageTime == null) { + mStartedPackageTime = new MutableLong(0); + mPackageTimes.put(packageName, mStartedPackageTime); + } + } + } + + public void stop() { + if (mStartedTime != 0) { + long elapsedTime = SystemClock.elapsedRealtime() - mStartedTime; + mTotalTime += elapsedTime; + if (mStartedPackageTime != null) { + mStartedPackageTime.value += elapsedTime; + } + mStartedPackage = null; + mStartedPackageTime = null; + } + } + + public void deliverResult(Context context) { + stop(); + Bundle extras = new Bundle(); + extras.putLong(ActivityOptions.EXTRA_USAGE_REPORT_TIME, mTotalTime); + Bundle pkgs = new Bundle(); + for (int i=mPackageTimes.size()-1; i>=0; i--) { + pkgs.putLong(mPackageTimes.keyAt(i), mPackageTimes.valueAt(i).value); + } + extras.putBundle(ActivityOptions.EXTRA_USAGE_REPORT_PACKAGES, pkgs); + Intent fillinIntent = new Intent(); + fillinIntent.putExtras(extras); + try { + mReceiver.send(context, 0, fillinIntent); + } catch (PendingIntent.CanceledException e) { + } + } + + public void dumpWithHeader(PrintWriter pw, String prefix, boolean details) { + pw.print(prefix); pw.print("AppTimeTracker #"); + pw.print(Integer.toHexString(System.identityHashCode(this))); + pw.println(":"); + dump(pw, prefix + " ", details); + } + + public void dump(PrintWriter pw, String prefix, boolean details) { + pw.print(prefix); pw.print("mReceiver="); pw.println(mReceiver); + pw.print(prefix); pw.print("mTotalTime="); + TimeUtils.formatDuration(mTotalTime, pw); + pw.println(); + for (int i = 0; i < mPackageTimes.size(); i++) { + pw.print(prefix); pw.print("mPackageTime:"); pw.print(mPackageTimes.keyAt(i)); + pw.print("="); + TimeUtils.formatDuration(mPackageTimes.valueAt(i).value, pw); + pw.println(); + } + if (details && mStartedTime != 0) { + pw.print(prefix); pw.print("mStartedTime="); + TimeUtils.formatDuration(SystemClock.elapsedRealtime(), mStartedTime, pw); + pw.println(); + pw.print(prefix); pw.print("mStartedPackage="); pw.println(mStartedPackage); + } + } +} diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index 33d40ad8e6363..c105491c6b081 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -75,5 +75,6 @@ + diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 4281c68cb3c9c..fc66d6d0953cf 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AlertDialog; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -412,6 +413,20 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Track time").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, "We are sharing this with you!"); + ActivityOptions options = ActivityOptions.makeBasic(); + Intent receiveIntent = new Intent(ActivityTestMain.this, TrackTimeReceiver.class); + receiveIntent.putExtra("something", "yeah, this is us!"); + options.requestUsageTimeReport(PendingIntent.getBroadcast(ActivityTestMain.this, + 0, receiveIntent, PendingIntent.FLAG_CANCEL_CURRENT)); + startActivity(Intent.createChooser(intent, "Who do you love?"), options.toBundle()); + return true; + } + }); return true; } diff --git a/tests/ActivityTests/src/com/google/android/test/activity/TrackTimeReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/TrackTimeReceiver.java new file mode 100644 index 0000000000000..c30d33a36ae5d --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/TrackTimeReceiver.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2015 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.google.android.test.activity; + +import android.app.ActivityOptions; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public class TrackTimeReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Bundle data = intent.getExtras(); + data.getLong(ActivityOptions.EXTRA_USAGE_REPORT_TIME); + Log.i("ActivityTest", "Received time: " + data); + } +}