diff options
| author | 2019-01-28 22:30:26 +0000 | |
|---|---|---|
| committer | 2019-01-28 22:30:26 +0000 | |
| commit | d10f95dcefac4be3e9cd76a15ff54a606ea05ee8 (patch) | |
| tree | 62a755c290101f359e4d9cd50e03d839c7724cc6 | |
| parent | 5e55e809a722f1540630062d356c504169b3a688 (diff) | |
| parent | 2f558d2659cf1c13c1672f93e7dd420cb887e8d3 (diff) | |
Merge "Defer broadcasts to slow-handling apps"
15 files changed, 1379 insertions, 128 deletions
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index fb65da14a087..412d7f45986c 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -277,6 +277,7 @@ interface IActivityManager { void unstableProviderDied(in IBinder connection); boolean isIntentSenderAnActivity(in IIntentSender sender); boolean isIntentSenderAForegroundService(in IIntentSender sender); + boolean isIntentSenderABroadcast(in IIntentSender sender); int startActivityAsUser(in IApplicationThread caller, in String callingPackage, in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, int flags, in ProfilerInfo profilerInfo, diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 75d95b2c2508..55014ebf1fcb 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -1113,6 +1113,19 @@ public final class PendingIntent implements Parcelable { /** * @hide + * Check whether this PendingIntent will launch an Activity. + */ + public boolean isBroadcast() { + try { + return ActivityManager.getService() + .isIntentSenderABroadcast(mTarget); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Return the Intent of this PendingIntent. */ @UnsupportedAppUsage diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f04c75268391..927c729758b5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11799,6 +11799,44 @@ public final class Settings { public static final String SYNC_MANAGER_CONSTANTS = "sync_manager_constants"; /** + * Broadcast dispatch tuning parameters specific to foreground broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true" + * + * The following keys are supported: + * <pre> + * bcast_timeout (long) + * bcast_slow_time (long) + * bcast_deferral (long) + * bcast_deferral_decay_factor (float) + * bcast_deferral_floor (long) + * </pre> + * + * @hide + */ + public static final String BROADCAST_FG_CONSTANTS = "bcast_fg_constants"; + + /** + * Broadcast dispatch tuning parameters specific to background broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true". + * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys. + * + * @hide + */ + public static final String BROADCAST_BG_CONSTANTS = "bcast_bg_constants"; + + /** + * Broadcast dispatch tuning parameters specific to specific "offline" broadcasts. + * + * This is encoded as a key=value list, separated by commas. Ex: "foo=1,bar=true". + * See {@link #BROADCAST_FG_CONSTANTS} for the list of supported keys. + * + * @hide + */ + public static final String BROADCAST_OFFLOAD_CONSTANTS = "bcast_offload_constants"; + + /** * Whether or not App Standby feature is enabled by system. This controls throttling of apps * based on usage patterns and predictions. Platform will turn on this feature if both this * flag and {@link #ADAPTIVE_BATTERY_MANAGEMENT_ENABLED} is on. diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index bd7f8527fc6f..2a29f8336adc 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -132,6 +132,9 @@ public class SettingsBackupTest { Settings.Global.AUTOMATIC_POWER_SAVER_MODE, Settings.Global.BACKGROUND_ACTIVITY_STARTS_ENABLED, Settings.Global.BATTERY_CHARGING_STATE_UPDATE_DELAY, + Settings.Global.BROADCAST_BG_CONSTANTS, + Settings.Global.BROADCAST_FG_CONSTANTS, + Settings.Global.BROADCAST_OFFLOAD_CONSTANTS, Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD, Settings.Global.BATTERY_DISCHARGE_THRESHOLD, Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS, diff --git a/services/core/java/com/android/server/AlarmManagerInternal.java b/services/core/java/com/android/server/AlarmManagerInternal.java index dbff957a0da7..275661084aa3 100644 --- a/services/core/java/com/android/server/AlarmManagerInternal.java +++ b/services/core/java/com/android/server/AlarmManagerInternal.java @@ -17,5 +17,15 @@ package com.android.server; public interface AlarmManagerInternal { - void removeAlarmsForUid(int uid); + // Some other components in the system server need to know about + // broadcast alarms currently in flight + public interface InFlightListener { + /** There is now an alarm pending delivery to the given app */ + void broadcastAlarmPending(int recipientUid); + /** A broadcast alarm targeted to the given app has completed delivery */ + void broadcastAlarmComplete(int recipientUid); + } + + public void removeAlarmsForUid(int uid); + public void registerInFlightListener(InFlightListener callback); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index e3dcb7d331cf..10b532700d1b 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -197,6 +197,8 @@ class AlarmManagerService extends SystemService { PowerManager.WakeLock mWakeLock; ArrayList<Alarm> mPendingNonWakeupAlarms = new ArrayList<>(); ArrayList<InFlight> mInFlight = new ArrayList<>(); + private final ArrayList<AlarmManagerInternal.InFlightListener> mInFlightListeners = + new ArrayList<>(); AlarmHandler mHandler; AppWakeupHistory mAppWakeupHistory; ClockReceiver mClockReceiver; @@ -1315,6 +1317,10 @@ class AlarmManagerService extends SystemService { mAlarmType = alarm.type; } + boolean isBroadcast() { + return mPendingIntent != null && mPendingIntent.isBroadcast(); + } + @Override public String toString() { return "InFlight{" @@ -1354,6 +1360,20 @@ class AlarmManagerService extends SystemService { } } + private void notifyBroadcastAlarmPendingLocked(int uid) { + final int numListeners = mInFlightListeners.size(); + for (int i = 0; i < numListeners; i++) { + mInFlightListeners.get(i).broadcastAlarmPending(uid); + } + } + + private void notifyBroadcastAlarmCompleteLocked(int uid) { + final int numListeners = mInFlightListeners.size(); + for (int i = 0; i < numListeners; i++) { + mInFlightListeners.get(i).broadcastAlarmComplete(uid); + } + } + static final class FilterStats { final BroadcastStats mBroadcastStats; final String mTag; @@ -1976,6 +1996,13 @@ class AlarmManagerService extends SystemService { removeLocked(uid); } } + + @Override + public void registerInFlightListener(InFlightListener callback) { + synchronized (mLock) { + mInFlightListeners.add(callback); + } + } } /** @@ -4426,7 +4453,11 @@ class AlarmManagerService extends SystemService { private InFlight removeLocked(PendingIntent pi, Intent intent) { for (int i = 0; i < mInFlight.size(); i++) { - if (mInFlight.get(i).mPendingIntent == pi) { + final InFlight inflight = mInFlight.get(i); + if (inflight.mPendingIntent == pi) { + if (pi.isBroadcast()) { + notifyBroadcastAlarmCompleteLocked(inflight.mUid); + } return mInFlight.remove(i); } } @@ -4649,6 +4680,9 @@ class AlarmManagerService extends SystemService { final InFlight inflight = new InFlight(AlarmManagerService.this, alarm, nowELAPSED); mInFlight.add(inflight); mBroadcastRefCount++; + if (inflight.isBroadcast()) { + notifyBroadcastAlarmPendingLocked(alarm.uid); + } if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 0aaea2f62de0..e42666c6a637 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -48,6 +48,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BROADCAST = DEBUG_ALL || false; static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; + static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; static final boolean DEBUG_MU = DEBUG_ALL || false; static final boolean DEBUG_NETWORK = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index eb643b670fcd..d9f3c02e4292 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -224,7 +224,6 @@ import android.hardware.display.DisplayManagerInternal; import android.location.LocationManager; import android.media.audiofx.AudioEffect; import android.net.Proxy; -import android.net.ProxyInfo; import android.net.Uri; import android.os.AppZygote; import android.os.BatteryStats; @@ -2090,6 +2089,8 @@ public class ActivityManagerService extends IActivityManager.Stub if (phase == PHASE_SYSTEM_SERVICES_READY) { mService.mBatteryStatsService.systemServicesReady(); mService.mServices.systemServicesReady(); + } else if (phase == PHASE_ACTIVITY_MANAGER_READY) { + mService.startBroadcastObservers(); } } @@ -2266,12 +2267,27 @@ public class ActivityManagerService extends IActivityManager.Stub mProcessList.init(this, activeUids); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); + // Broadcast policy parameters + final BroadcastConstants foreConstants = new BroadcastConstants( + Settings.Global.BROADCAST_FG_CONSTANTS); + foreConstants.TIMEOUT = BROADCAST_FG_TIMEOUT; + + final BroadcastConstants backConstants = new BroadcastConstants( + Settings.Global.BROADCAST_BG_CONSTANTS); + backConstants.TIMEOUT = BROADCAST_BG_TIMEOUT; + + final BroadcastConstants offloadConstants = new BroadcastConstants( + Settings.Global.BROADCAST_OFFLOAD_CONSTANTS); + offloadConstants.TIMEOUT = BROADCAST_BG_TIMEOUT; + // by default, no "slow" policy in this queue + offloadConstants.SLOW_TIME = Integer.MAX_VALUE; + mFgBroadcastQueue = new BroadcastQueue(this, mHandler, - "foreground", BROADCAST_FG_TIMEOUT, false); + "foreground", foreConstants, false); mBgBroadcastQueue = new BroadcastQueue(this, mHandler, - "background", BROADCAST_BG_TIMEOUT, true); + "background", backConstants, true); mOffloadBroadcastQueue = new BroadcastQueue(this, mHandler, - "offload", BROADCAST_BG_TIMEOUT, true); + "offload", offloadConstants, true); mBroadcastQueues[0] = mFgBroadcastQueue; mBroadcastQueues[1] = mBgBroadcastQueue; mBroadcastQueues[2] = mOffloadBroadcastQueue; @@ -5226,6 +5242,15 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override + public boolean isIntentSenderABroadcast(IIntentSender pendingResult) { + if (pendingResult instanceof PendingIntentRecord) { + final PendingIntentRecord res = (PendingIntentRecord) pendingResult; + return res.key.type == ActivityManager.INTENT_SENDER_BROADCAST; + } + return false; + } + + @Override public Intent getIntentForIntentSender(IIntentSender pendingResult) { enforceCallingPermission(Manifest.permission.GET_INTENT_SENDER_INTENT, "getIntentForIntentSender()"); @@ -7211,7 +7236,6 @@ public class ActivityManagerService extends IActivityManager.Stub synchronized (this) { mSystemProvidersInstalled = true; } - mConstants.start(mContext.getContentResolver()); mCoreSettingsObserver = new CoreSettingsObserver(this); mActivityTaskManager.installSystemProviders(); @@ -8711,6 +8735,12 @@ public class ActivityManagerService extends IActivityManager.Stub } } + private void startBroadcastObservers() { + for (BroadcastQueue queue : mBroadcastQueues) { + queue.start(mContext.getContentResolver()); + } + } + private void updateForceBackgroundCheck(boolean enabled) { synchronized (this) { if (mForceBackgroundCheck != enabled) { @@ -14909,10 +14939,7 @@ public class ActivityManagerService extends IActivityManager.Stub resultData, resultExtras, ordered, sticky, false, userId, allowBackgroundActivityStarts); - if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r - + ": prev had " + queue.mOrderedBroadcasts.size()); - if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, - "Enqueueing broadcast " + r.intent.getAction()); + if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r); final BroadcastRecord oldRecord = replacePending ? queue.replaceOrderedBroadcastLocked(r) : null; @@ -15788,13 +15815,12 @@ public class ActivityManagerService extends IActivityManager.Stub * Returns true if things are idle enough to perform GCs. */ private final boolean canGcNowLocked() { - boolean processingBroadcasts = false; for (BroadcastQueue q : mBroadcastQueues) { - if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) { - processingBroadcasts = true; + if (!q.mParallelBroadcasts.isEmpty() || !q.mDispatcher.isEmpty()) { + return false; } } - return !processingBroadcasts && mAtmInternal.canGcNow(); + return mAtmInternal.canGcNow(); } /** diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java new file mode 100644 index 000000000000..820caf12ac84 --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastConstants.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 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 com.android.server.am; + +import android.content.ContentResolver; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.KeyValueListParser; +import android.util.Slog; +import android.util.TimeUtils; + +import java.io.PrintWriter; + +/** + * Tunable parameters for broadcast dispatch policy + */ +public class BroadcastConstants { + private static final String TAG = "BroadcastConstants"; + + // Value element names within the Settings record + static final String KEY_TIMEOUT = "bcast_timeout"; + static final String KEY_SLOW_TIME = "bcast_slow_time"; + static final String KEY_DEFERRAL = "bcast_deferral"; + static final String KEY_DEFERRAL_DECAY_FACTOR = "bcast_deferral_decay_factor"; + static final String KEY_DEFERRAL_FLOOR = "bcast_deferral_floor"; + + // All time intervals are in milliseconds + private static final long DEFAULT_TIMEOUT = 10_000; + private static final long DEFAULT_SLOW_TIME = 5_000; + private static final long DEFAULT_DEFERRAL = 5_000; + private static final float DEFAULT_DEFERRAL_DECAY_FACTOR = 0.75f; + private static final long DEFAULT_DEFERRAL_FLOOR = 0; + + // All time constants are in milliseconds + + // Timeout period for this broadcast queue + public long TIMEOUT = DEFAULT_TIMEOUT; + // Handling time above which we declare that a broadcast recipient was "slow". Any + // value <= zero is interpreted as disabling broadcast deferral policy entirely. + public long SLOW_TIME = DEFAULT_SLOW_TIME; + // How long to initially defer broadcasts, if an app is slow to handle one + public long DEFERRAL = DEFAULT_DEFERRAL; + // Decay factor for successive broadcasts' deferral time + public float DEFERRAL_DECAY_FACTOR = DEFAULT_DEFERRAL_DECAY_FACTOR; + // Minimum that the deferral time can decay to until the backlog fully clears + public long DEFERRAL_FLOOR = DEFAULT_DEFERRAL_FLOOR; + + // Settings override tracking for this instance + private String mSettingsKey; + private SettingsObserver mSettingsObserver; + private ContentResolver mResolver; + private final KeyValueListParser mParser = new KeyValueListParser(','); + + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange) { + updateConstants(); + } + } + + // A given constants instance is configured to observe specific keys from which + // that instance's values are drawn. + public BroadcastConstants(String settingsKey) { + mSettingsKey = settingsKey; + } + + /** + * Spin up the observer lazily, since it can only happen once the settings provider + * has been brought into service + */ + public void startObserving(Handler handler, ContentResolver resolver) { + mResolver = resolver; + + mSettingsObserver = new SettingsObserver(handler); + mResolver.registerContentObserver(Settings.Global.getUriFor(mSettingsKey), + false, mSettingsObserver); + + updateConstants(); + } + + private void updateConstants() { + synchronized (mParser) { + try { + mParser.setString(Settings.Global.getString(mResolver, mSettingsKey)); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Bad broadcast settings in key '" + mSettingsKey + "'", e); + return; + } + + // Unspecified fields retain their current value rather than revert to default + TIMEOUT = mParser.getLong(KEY_TIMEOUT, TIMEOUT); + SLOW_TIME = mParser.getLong(KEY_SLOW_TIME, SLOW_TIME); + DEFERRAL = mParser.getLong(KEY_DEFERRAL, DEFERRAL); + DEFERRAL_DECAY_FACTOR = mParser.getFloat(KEY_DEFERRAL_DECAY_FACTOR, + DEFERRAL_DECAY_FACTOR); + DEFERRAL_FLOOR = mParser.getLong(KEY_DEFERRAL_FLOOR, DEFERRAL_FLOOR); + } + } + + /** + * Standard dumpsys support; invoked from BroadcastQueue dump + */ + public void dump(PrintWriter pw) { + synchronized (mParser) { + pw.println(); + pw.print(" Broadcast parameters (key="); + pw.print(mSettingsKey); + pw.print(", observing="); + pw.print(mSettingsObserver != null); + pw.println("):"); + + pw.print(" "); pw.print(KEY_TIMEOUT); pw.print(" = "); + TimeUtils.formatDuration(TIMEOUT, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_SLOW_TIME); pw.print(" = "); + TimeUtils.formatDuration(SLOW_TIME, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_DEFERRAL); pw.print(" = "); + TimeUtils.formatDuration(DEFERRAL, pw); + pw.println(); + + pw.print(" "); pw.print(KEY_DEFERRAL_DECAY_FACTOR); pw.print(" = "); + pw.println(DEFERRAL_DECAY_FACTOR); + + pw.print(" "); pw.print(KEY_DEFERRAL_FLOOR); pw.print(" = "); + TimeUtils.formatDuration(DEFERRAL_FLOOR, pw); + pw.println(); + } + } +} diff --git a/services/core/java/com/android/server/am/BroadcastDispatcher.java b/services/core/java/com/android/server/am/BroadcastDispatcher.java new file mode 100644 index 000000000000..0d46379112aa --- /dev/null +++ b/services/core/java/com/android/server/am/BroadcastDispatcher.java @@ -0,0 +1,687 @@ +/* + * Copyright (C) 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 com.android.server.am; + +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST; +import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_DEFERRAL; + +import android.content.Intent; +import android.os.Handler; +import android.os.SystemClock; +import android.util.Slog; +import android.util.SparseIntArray; +import android.util.proto.ProtoOutputStream; + +import com.android.server.AlarmManagerInternal; +import com.android.server.LocalServices; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Set; + +/** + * Manages ordered broadcast delivery, applying policy to mitigate the effects of + * slow receivers. + */ +public class BroadcastDispatcher { + private static final String TAG = "BroadcastDispatcher"; + + // Deferred broadcasts to one app; times are all uptime time base like + // other broadcast-related timekeeping + static class Deferrals { + final int uid; + long deferredAt; // when we started deferring + long deferredBy; // how long did we defer by last time? + long deferUntil; // when does the next element become deliverable? + int alarmCount; + + final ArrayList<BroadcastRecord> broadcasts; + + Deferrals(int uid, long now, long backoff, int count) { + this.uid = uid; + this.deferredAt = now; + this.deferredBy = backoff; + this.deferUntil = now + backoff; + this.alarmCount = count; + broadcasts = new ArrayList<>(); + } + + void add(BroadcastRecord br) { + broadcasts.add(br); + } + + void writeToProto(ProtoOutputStream proto, long fieldId) { + for (BroadcastRecord br : broadcasts) { + br.writeToProto(proto, fieldId); + } + } + + void dumpLocked(Dumper d) { + for (BroadcastRecord br : broadcasts) { + d.dump(br); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(128); + sb.append("Deferrals{uid="); + sb.append(uid); + sb.append(", deferUntil="); + sb.append(deferUntil); + sb.append(", #broadcasts="); + sb.append(broadcasts.size()); + sb.append("}"); + return sb.toString(); + } + } + + // Carrying dump formatting state across multiple concatenated datasets + class Dumper { + final PrintWriter mPw; + final String mQueueName; + final String mDumpPackage; + final SimpleDateFormat mSdf; + boolean mPrinted; + boolean mNeedSep; + String mHeading; + String mLabel; + int mOrdinal; + + Dumper(PrintWriter pw, String queueName, String dumpPackage, SimpleDateFormat sdf) { + mPw = pw; + mQueueName = queueName; + mDumpPackage = dumpPackage; + mSdf = sdf; + + mPrinted = false; + mNeedSep = true; + } + + void setHeading(String heading) { + mHeading = heading; + mPrinted = false; + } + + void setLabel(String label) { + //" Active Ordered Broadcast " + mQueueName + " #" + i + ":" + mLabel = " " + label + " " + mQueueName + " #"; + mOrdinal = 0; + } + + boolean didPrint() { + return mPrinted; + } + + void dump(BroadcastRecord br) { + if (mDumpPackage == null || mDumpPackage.equals(br.callerPackage)) { + if (!mPrinted) { + if (mNeedSep) { + mPw.println(); + } + mPrinted = true; + mNeedSep = true; + mPw.println(" " + mHeading + " [" + mQueueName + "]:"); + } + mPw.println(mLabel + mOrdinal + ":"); + mOrdinal++; + + br.dump(mPw, " ", mSdf); + } + } + } + + private final Object mLock; + private final BroadcastQueue mQueue; + private final BroadcastConstants mConstants; + private final Handler mHandler; + private AlarmManagerInternal mAlarm; + + // Current alarm targets; mapping uid -> in-flight alarm count + final SparseIntArray mAlarmUids = new SparseIntArray(); + final AlarmManagerInternal.InFlightListener mAlarmListener = + new AlarmManagerInternal.InFlightListener() { + @Override + public void broadcastAlarmPending(final int recipientUid) { + synchronized (mLock) { + final int newCount = mAlarmUids.get(recipientUid, 0) + 1; + mAlarmUids.put(recipientUid, newCount); + // any deferred broadcasts to this app now get fast-tracked + final int numEntries = mDeferredBroadcasts.size(); + for (int i = 0; i < numEntries; i++) { + if (recipientUid == mDeferredBroadcasts.get(i).uid) { + Deferrals d = mDeferredBroadcasts.remove(i); + mAlarmBroadcasts.add(d); + break; + } + } + } + } + + @Override + public void broadcastAlarmComplete(final int recipientUid) { + synchronized (mLock) { + final int newCount = mAlarmUids.get(recipientUid, 0) - 1; + if (newCount >= 0) { + mAlarmUids.put(recipientUid, newCount); + } else { + Slog.wtf(TAG, "Undercount of broadcast alarms in flight for " + recipientUid); + mAlarmUids.put(recipientUid, 0); + } + + // No longer an alarm target, so resume ordinary deferral policy + if (newCount <= 0) { + final int numEntries = mAlarmBroadcasts.size(); + for (int i = 0; i < numEntries; i++) { + if (recipientUid == mAlarmBroadcasts.get(i).uid) { + Deferrals d = mAlarmBroadcasts.remove(i); + insertLocked(mDeferredBroadcasts, d); + break; + } + } + } + } + } + }; + + // Queue recheck operation used to tickle broadcast delivery when appropriate + final Runnable mScheduleRunnable = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.v(TAG, "Deferral recheck of pending broadcasts"); + } + mQueue.scheduleBroadcastsLocked(); + mRecheckScheduled = false; + } + } + }; + private boolean mRecheckScheduled = false; + + // Usual issuance-order outbound queue + private final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>(); + // General deferrals not holding up alarms + private final ArrayList<Deferrals> mDeferredBroadcasts = new ArrayList<>(); + // Deferrals that *are* holding up alarms; ordered by alarm dispatch time + private final ArrayList<Deferrals> mAlarmBroadcasts = new ArrayList<>(); + + // Next outbound broadcast, established by getNextBroadcastLocked() + private BroadcastRecord mCurrentBroadcast; + + /** + * Constructed & sharing a lock with its associated BroadcastQueue instance + */ + public BroadcastDispatcher(BroadcastQueue queue, BroadcastConstants constants, + Handler handler, Object lock) { + mQueue = queue; + mConstants = constants; + mHandler = handler; + mLock = lock; + } + + /** + * Spin up the integration with the alarm manager service; done lazily to manage + * service availability ordering during boot. + */ + public void start() { + // Set up broadcast alarm tracking + mAlarm = LocalServices.getService(AlarmManagerInternal.class); + mAlarm.registerInFlightListener(mAlarmListener); + } + + /** + * Standard contents-are-empty check + */ + public boolean isEmpty() { + synchronized (mLock) { + return mCurrentBroadcast == null + && mOrderedBroadcasts.isEmpty() + && mDeferredBroadcasts.isEmpty() + && mAlarmBroadcasts.isEmpty(); + } + } + + /** + * Not quite the traditional size() measurement; includes any in-process but + * not yet retired active outbound broadcast. + */ + public int totalUndelivered() { + synchronized (mLock) { + return mAlarmBroadcasts.size() + + mDeferredBroadcasts.size() + + mOrderedBroadcasts.size() + + (mCurrentBroadcast == null ? 0 : 1); + } + } + + // ---------------------------------- + // BroadcastQueue operation support + + void enqueueOrderedBroadcastLocked(BroadcastRecord r) { + mOrderedBroadcasts.add(r); + } + + // Returns the now-replaced broadcast record, or null if none + BroadcastRecord replaceBroadcastLocked(BroadcastRecord r, String typeForLogging) { + // Simple case, in the ordinary queue. + BroadcastRecord old = replaceBroadcastLocked(mOrderedBroadcasts, r, typeForLogging); + + // If we didn't find it, less-simple: in a deferral queue? + if (old == null) { + old = replaceDeferredBroadcastLocked(mAlarmBroadcasts, r, typeForLogging); + } + if (old == null) { + old = replaceDeferredBroadcastLocked(mDeferredBroadcasts, r, typeForLogging); + } + return old; + } + + private BroadcastRecord replaceDeferredBroadcastLocked(ArrayList<Deferrals> list, + BroadcastRecord r, String typeForLogging) { + BroadcastRecord old; + final int numEntries = list.size(); + for (int i = 0; i < numEntries; i++) { + final Deferrals d = list.get(i); + old = replaceBroadcastLocked(d.broadcasts, r, typeForLogging); + if (old != null) { + return old; + } + } + return null; + } + + private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> list, + BroadcastRecord r, String typeForLogging) { + BroadcastRecord old; + final Intent intent = r.intent; + // Any in-flight broadcast has already been popped, and cannot be replaced. + // (This preserves existing behavior of the replacement API) + for (int i = list.size() - 1; i >= 0; i++) { + old = list.get(i); + if (old.userId == r.userId && intent.filterEquals(old.intent)) { + if (DEBUG_BROADCAST) { + Slog.v(TAG, "***** Replacing " + typeForLogging + + " [" + mQueue.mQueueName + "]: " + intent); + } + // Clone deferral state too if any + r.deferred = old.deferred; + list.set(i, r); + return old; + } + } + return null; + } + + boolean cleanupDisabledPackageReceiversLocked(final String packageName, + Set<String> filterByClasses, final int userId, final boolean doit) { + // Note: fast short circuits when 'doit' is false, as soon as we hit any + // "yes we would do something" circumstance + boolean didSomething = cleanupBroadcastListDisabledReceiversLocked(mOrderedBroadcasts, + packageName, filterByClasses, userId, doit); + if (doit || !didSomething) { + didSomething |= cleanupDeferralsListDisabledReceiversLocked(mAlarmBroadcasts, + packageName, filterByClasses, userId, doit); + } + if (doit || !didSomething) { + didSomething |= cleanupDeferralsListDisabledReceiversLocked(mDeferredBroadcasts, + packageName, filterByClasses, userId, doit); + } + if ((doit || !didSomething) && mCurrentBroadcast != null) { + didSomething |= mCurrentBroadcast.cleanupDisabledPackageReceiversLocked( + packageName, filterByClasses, userId, doit); + } + + return didSomething; + } + + private boolean cleanupDeferralsListDisabledReceiversLocked(ArrayList<Deferrals> list, + final String packageName, Set<String> filterByClasses, final int userId, + final boolean doit) { + boolean didSomething = false; + for (Deferrals d : list) { + didSomething = cleanupBroadcastListDisabledReceiversLocked(d.broadcasts, + packageName, filterByClasses, userId, doit); + if (!doit && didSomething) { + return true; + } + } + return didSomething; + } + + private boolean cleanupBroadcastListDisabledReceiversLocked(ArrayList<BroadcastRecord> list, + final String packageName, Set<String> filterByClasses, final int userId, + final boolean doit) { + boolean didSomething = false; + for (BroadcastRecord br : list) { + didSomething |= br.cleanupDisabledPackageReceiversLocked(packageName, + filterByClasses, userId, doit); + if (!doit && didSomething) { + return true; + } + } + return didSomething; + } + + /** + * Standard proto dump entry point + */ + public void writeToProto(ProtoOutputStream proto, long fieldId) { + if (mCurrentBroadcast != null) { + mCurrentBroadcast.writeToProto(proto, fieldId); + } + for (Deferrals d : mAlarmBroadcasts) { + d.writeToProto(proto, fieldId); + } + for (BroadcastRecord br : mOrderedBroadcasts) { + br.writeToProto(proto, fieldId); + } + for (Deferrals d : mDeferredBroadcasts) { + d.writeToProto(proto, fieldId); + } + } + + // ---------------------------------- + // Dispatch & deferral management + + public BroadcastRecord getActiveBroadcastLocked() { + return mCurrentBroadcast; + } + + /** + * If there is a deferred broadcast that is being sent to an alarm target, return + * that one. If there's no deferred alarm target broadcast but there is one + * that has reached the end of its deferral, return that. + * + * This stages the broadcast internally until it is retired, and returns that + * staged record if this is called repeatedly, until retireBroadcast(r) is called. + */ + public BroadcastRecord getNextBroadcastLocked(final long now) { + if (mCurrentBroadcast != null) { + return mCurrentBroadcast; + } + + BroadcastRecord next = null; + if (!mAlarmBroadcasts.isEmpty()) { + next = popLocked(mAlarmBroadcasts); + if (DEBUG_BROADCAST_DEFERRAL && next != null) { + Slog.i(TAG, "Next broadcast from alarm targets: " + next); + } + } + + if (next == null && !mDeferredBroadcasts.isEmpty()) { + for (int i = 0; i < mDeferredBroadcasts.size(); i++) { + Deferrals d = mDeferredBroadcasts.get(i); + if (now < d.deferUntil) { + // No more deferrals due + break; + } + + if (d.broadcasts.size() > 0) { + next = d.broadcasts.remove(0); + // apply deferral-interval decay policy and move this uid's + // deferred broadcasts down in the delivery queue accordingly + mDeferredBroadcasts.remove(i); // already 'd' + d.deferredBy = calculateDeferral(d.deferredBy); + d.deferUntil += d.deferredBy; + insertLocked(mDeferredBroadcasts, d); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Next broadcast from deferrals " + next + + ", deferUntil now " + d.deferUntil); + } + break; + } + } + } + + if (next == null && !mOrderedBroadcasts.isEmpty()) { + next = mOrderedBroadcasts.remove(0); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Next broadcast from main queue: " + next); + } + } + + mCurrentBroadcast = next; + return next; + } + + /** + * Called after the broadcast queue finishes processing the currently + * active broadcast (obtained by calling getNextBroadcastLocked()). + */ + public void retireBroadcastLocked(final BroadcastRecord r) { + // ERROR if 'r' is not the active broadcast + if (r != mCurrentBroadcast) { + Slog.wtf(TAG, "Retiring broadcast " + r + + " doesn't match current outgoing " + mCurrentBroadcast); + } + mCurrentBroadcast = null; + } + + /** + * Called prior to broadcast dispatch to check whether the intended + * recipient is currently subject to deferral policy. + */ + public boolean isDeferringLocked(final int uid) { + Deferrals d = findUidLocked(uid); + if (d != null && d.broadcasts.isEmpty()) { + // once we've caught up with deferred broadcasts to this uid + // and time has advanced sufficiently that we wouldn't be + // deferring newly-enqueued ones, we're back to normal policy. + if (SystemClock.uptimeMillis() >= d.deferUntil) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "No longer deferring broadcasts to uid " + d.uid); + } + removeDeferral(d); + return false; + } + } + return (d != null); + } + + /** + * Defer broadcasts for the given app. If 'br' is non-null, this also makes + * sure that broadcast record is enqueued as the next upcoming broadcast for + * the app. + */ + public void startDeferring(final int uid) { + synchronized (mLock) { + Deferrals d = findUidLocked(uid); + + // If we're not yet tracking this app, set up that bookkeeping + if (d == null) { + // Start a new deferral + final long now = SystemClock.uptimeMillis(); + d = new Deferrals(uid, + now, + mConstants.DEFERRAL, + mAlarmUids.get(uid, 0)); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Now deferring broadcasts to " + uid + + " until " + d.deferUntil); + } + // where it goes depends on whether it is coming into an alarm-related situation + if (d.alarmCount == 0) { + // common case, put it in the ordinary priority queue + insertLocked(mDeferredBroadcasts, d); + scheduleDeferralCheckLocked(true); + } else { + // alarm-related: strict order-encountered + mAlarmBroadcasts.add(d); + } + } else { + // We're already deferring, but something was slow again. Reset the + // deferral decay progression. + d.deferredBy = mConstants.DEFERRAL; + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Uid " + uid + " slow again, deferral interval reset to " + + d.deferredBy); + } + } + } + } + + /** + * Key entry point when a broadcast about to be delivered is instead + * set aside for deferred delivery + */ + public void addDeferredBroadcast(final int uid, BroadcastRecord br) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Enqueuing deferred broadcast " + br); + } + synchronized (mLock) { + Deferrals d = findUidLocked(uid); + if (d == null) { + Slog.wtf(TAG, "Adding deferred broadcast but not tracking " + uid); + } else { + if (br == null) { + Slog.wtf(TAG, "Deferring null broadcast to " + uid); + } else { + br.deferred = true; + d.add(br); + } + } + } + } + + /** + * When there are deferred broadcasts, we need to make sure to recheck the + * dispatch queue when they come due. Alarm-sensitive deferrals get dispatched + * aggressively, so we only need to use the ordinary deferrals timing to figure + * out when to recheck. + */ + public void scheduleDeferralCheckLocked(boolean force) { + if ((force || !mRecheckScheduled) && !mDeferredBroadcasts.isEmpty()) { + final Deferrals d = mDeferredBroadcasts.get(0); + if (!d.broadcasts.isEmpty()) { + mHandler.removeCallbacks(mScheduleRunnable); + mHandler.postAtTime(mScheduleRunnable, d.deferUntil); + mRecheckScheduled = true; + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Scheduling deferred broadcast recheck at " + d.deferUntil); + } + } + } + } + + // ---------------------------------- + + /** + * If broadcasts to this uid are being deferred, find the deferrals record about it. + * @return null if this uid's broadcasts are not being deferred + */ + private Deferrals findUidLocked(final int uid) { + // The common case is that they it isn't also an alarm target... + Deferrals d = findUidLocked(uid, mDeferredBroadcasts); + // ...but if not there, also check alarm-prioritized deferrals + if (d == null) { + d = findUidLocked(uid, mAlarmBroadcasts); + } + return d; + } + + /** + * Remove the given deferral record from whichever queue it might be in at present + * @return true if the deferral was in fact found, false if this made no changes + */ + private boolean removeDeferral(Deferrals d) { + boolean didRemove = mDeferredBroadcasts.remove(d); + if (!didRemove) { + didRemove = mAlarmBroadcasts.remove(d); + } + return didRemove; + } + + /** + * Find the deferrals record for the given uid in the given list + */ + private static Deferrals findUidLocked(final int uid, ArrayList<Deferrals> list) { + final int numElements = list.size(); + for (int i = 0; i < numElements; i++) { + Deferrals d = list.get(i); + if (uid == d.uid) { + return d; + } + } + return null; + } + + /** + * Pop the next broadcast record from the head of the given deferrals list, + * if one exists. + */ + private static BroadcastRecord popLocked(ArrayList<Deferrals> list) { + final Deferrals d = list.get(0); + return d.broadcasts.isEmpty() ? null : d.broadcasts.remove(0); + } + + /** + * Insert the given Deferrals into the priority queue, sorted by defer-until milestone + */ + private static void insertLocked(ArrayList<Deferrals> list, Deferrals d) { + // Simple linear search is appropriate here because we expect to + // have very few entries in the deferral lists (i.e. very few badly- + // behaving apps currently facing deferral) + int i; + final int numElements = list.size(); + for (i = 0; i < numElements; i++) { + if (d.deferUntil < list.get(i).deferUntil) { + break; + } + } + list.add(i, d); + } + + /** + * Calculate a new deferral time based on the previous time. This should decay + * toward zero, though a small nonzero floor is an option. + */ + private long calculateDeferral(long previous) { + return Math.max(mConstants.DEFERRAL_FLOOR, + (long) (previous * mConstants.DEFERRAL_DECAY_FACTOR)); + } + + // ---------------------------------- + + boolean dumpLocked(PrintWriter pw, String dumpPackage, String queueName, + SimpleDateFormat sdf) { + final Dumper dumper = new Dumper(pw, queueName, dumpPackage, sdf); + boolean printed = false; + + dumper.setHeading("Active ordered broadcasts"); + dumper.setLabel("Active Ordered Broadcast"); + for (Deferrals d : mAlarmBroadcasts) { + d.dumpLocked(dumper); + } + printed |= dumper.didPrint(); + + for (BroadcastRecord br : mOrderedBroadcasts) { + dumper.dump(br); + } + printed |= dumper.didPrint(); + + dumper.setHeading("Deferred ordered broadcasts"); + dumper.setLabel("Deferred Ordered Broadcast"); + for (Deferrals d : mDeferredBroadcasts) { + d.dumpLocked(dumper); + } + printed |= dumper.didPrint(); + + return printed; + } +} diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java index cdf6e0ede865..64a36ef66f12 100644 --- a/services/core/java/com/android/server/am/BroadcastQueue.java +++ b/services/core/java/com/android/server/am/BroadcastQueue.java @@ -24,6 +24,7 @@ import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.PendingIntent; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.IIntentReceiver; import android.content.IIntentSender; import android.content.Intent; @@ -45,6 +46,7 @@ import android.os.Trace; import android.os.UserHandle; import android.util.EventLog; import android.util.Slog; +import android.util.SparseIntArray; import android.util.StatsLog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; @@ -75,14 +77,15 @@ public final class BroadcastQueue { final ActivityManagerService mService; /** - * Recognizable moniker for this queue + * Behavioral parameters such as timeouts and deferral policy, tracking Settings + * for runtime configurability */ - final String mQueueName; + final BroadcastConstants mConstants; /** - * Timeout period for this queue's broadcasts + * Recognizable moniker for this queue */ - final long mTimeoutPeriod; + final String mQueueName; /** * If true, we can delay broadcasts while waiting services to finish in the previous @@ -100,13 +103,18 @@ public final class BroadcastQueue { final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>(); /** - * List of all active broadcasts that are to be executed one at a time. - * The object at the top of the list is the currently activity broadcasts; - * those after it are waiting for the top to finish. As with parallel - * broadcasts, separate background- and foreground-priority queues are - * maintained. + * Tracking of the ordered broadcast queue, including deferral policy and alarm + * prioritization. */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>(); + final BroadcastDispatcher mDispatcher; + + /** + * Refcounting for completion callbacks of split/deferred broadcasts. The key + * is an opaque integer token assigned lazily when a broadcast is first split + * into multiple BroadcastRecord objects. + */ + final SparseIntArray mSplitRefcounts = new SparseIntArray(); + private int mNextToken = 0; /** * Historical data of past broadcasts, for debugging. This is a ring buffer @@ -173,7 +181,8 @@ public final class BroadcastQueue { switch (msg.what) { case BROADCAST_INTENT_MSG: { if (DEBUG_BROADCAST) Slog.v( - TAG_BROADCAST, "Received BROADCAST_INTENT_MSG"); + TAG_BROADCAST, "Received BROADCAST_INTENT_MSG [" + + mQueueName + "]"); processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { @@ -201,12 +210,19 @@ public final class BroadcastQueue { } BroadcastQueue(ActivityManagerService service, Handler handler, - String name, long timeoutPeriod, boolean allowDelayBehindServices) { + String name, BroadcastConstants constants, boolean allowDelayBehindServices) { mService = service; mHandler = new BroadcastHandler(handler.getLooper()); mQueueName = name; - mTimeoutPeriod = timeoutPeriod; mDelayBehindServices = allowDelayBehindServices; + + mConstants = constants; + mDispatcher = new BroadcastDispatcher(this, mConstants, mHandler, mService); + } + + void start(ContentResolver resolver) { + mDispatcher.start(); + mConstants.startObserving(mHandler, resolver); } @Override @@ -224,7 +240,7 @@ public final class BroadcastQueue { } public void enqueueOrderedBroadcastLocked(BroadcastRecord r) { - mOrderedBroadcasts.add(r); + mDispatcher.enqueueOrderedBroadcastLocked(r); enqueueBroadcastHelper(r); } @@ -255,7 +271,7 @@ public final class BroadcastQueue { * the old one. */ public final BroadcastRecord replaceOrderedBroadcastLocked(BroadcastRecord r) { - return replaceBroadcastLocked(mOrderedBroadcasts, r, "ORDERED"); + return mDispatcher.replaceBroadcastLocked(r, "ORDERED"); } private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue, @@ -365,14 +381,17 @@ public final class BroadcastQueue { } } + // Skip the current receiver, if any, that is in flight to the given process public void skipCurrentReceiverLocked(ProcessRecord app) { BroadcastRecord r = null; - if (mOrderedBroadcasts.size() > 0) { - BroadcastRecord br = mOrderedBroadcasts.get(0); - if (br.curApp == app) { - r = br; - } + final BroadcastRecord curActive = mDispatcher.getActiveBroadcastLocked(); + if (curActive != null && curActive.curApp == app) { + // confirmed: the current active broadcast is to the given app + r = curActive; } + + // If the current active broadcast isn't this BUT we're waiting for + // mPendingBroadcast to spin up the target app, that's what we use. if (r == null && mPendingBroadcast != null && mPendingBroadcast.curApp == app) { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "[" + mQueueName + "] skip & discard pending app " + r); @@ -404,20 +423,29 @@ public final class BroadcastQueue { } public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) { - if (mOrderedBroadcasts.size() > 0) { - final BroadcastRecord r = mOrderedBroadcasts.get(0); - if (r != null && r.receiver == receiver) { - return r; - } + BroadcastRecord br = mDispatcher.getActiveBroadcastLocked(); + if (br != null && br.receiver == receiver) { + return br; } return null; } + // > 0 only, no worry about "eventual" recycling + private int nextSplitTokenLocked() { + int next = mNextToken + 1; + if (next <= 0) { + next = 1; + } + mNextToken = next; + return next; + } + public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) { final int state = r.state; final ActivityInfo receiver = r.curReceiver; final long finishTime = SystemClock.uptimeMillis(); + final long elapsed = finishTime - r.receiverTime; r.state = BroadcastRecord.IDLE; if (state == BroadcastRecord.IDLE) { Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); @@ -428,8 +456,22 @@ public final class BroadcastQueue { // If we're abandoning this broadcast before any receivers were actually spun up, // nextReceiver is zero; in which case time-to-process bookkeeping doesn't apply. if (r.nextReceiver > 0) { - r.duration[r.nextReceiver - 1] = finishTime - r.receiverTime; + r.duration[r.nextReceiver - 1] = elapsed; + } + + // if this receiver was slow, impose deferral policy on the app. This will kick in + // when processNextBroadcastLocked() next finds this uid as a receiver identity. + if (mConstants.SLOW_TIME > 0 && elapsed > mConstants.SLOW_TIME) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Broadcast receiver was slow: " + receiver + " br=" + r); + } + if (r.curApp != null) { + mDispatcher.startDeferring(r.curApp.uid); + } else { + Slog.d(TAG, "finish receiver curApp is null? " + r); + } } + r.receiver = null; r.intent.setComponent(null); if (r.curApp != null && r.curApp.curReceivers.contains(r)) { @@ -452,9 +494,10 @@ public final class BroadcastQueue { r.resultAbort = false; } + // If we want to wait behind services *AND* we're finishing the head/ + // active broadcast on its queue if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices - && r.queue.mOrderedBroadcasts.size() > 0 - && r.queue.mOrderedBroadcasts.get(0) == r) { + && r.queue.mDispatcher.getActiveBroadcastLocked() == r) { ActivityInfo nextReceiver; if (r.nextReceiver < r.receivers.size()) { Object obj = r.receivers.get(r.nextReceiver); @@ -488,8 +531,8 @@ public final class BroadcastQueue { } public void backgroundServicesFinishedLocked(int userId) { - if (mOrderedBroadcasts.size() > 0) { - BroadcastRecord br = mOrderedBroadcasts.get(0); + BroadcastRecord br = mDispatcher.getActiveBroadcastLocked(); + if (br != null) { if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) { Slog.i(TAG, "Resuming delayed broadcast"); br.curComponent = null; @@ -861,7 +904,7 @@ public final class BroadcastQueue { if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "processNextBroadcast [" + mQueueName + "]: " + mParallelBroadcasts.size() + " parallel broadcasts, " - + mOrderedBroadcasts.size() + " ordered broadcasts"); + + mDispatcher.totalUndelivered() + " ordered broadcasts"); mService.updateCpuStats(); @@ -937,8 +980,12 @@ public final class BroadcastQueue { boolean looped = false; do { - if (mOrderedBroadcasts.size() == 0) { - // No more broadcasts pending, so all done! + final long now = SystemClock.uptimeMillis(); + r = mDispatcher.getNextBroadcastLocked(now); + + if (r == null) { + // No more broadcasts are deliverable right now, so all done! + mDispatcher.scheduleDeferralCheckLocked(false); mService.scheduleAppGcsLocked(); if (looped) { // If we had finished the last ordered broadcast, then @@ -954,7 +1001,7 @@ public final class BroadcastQueue { return; } - r = mOrderedBroadcasts.get(0); + boolean forceReceive = false; // Ensure that even if something goes awry with the timeout @@ -967,9 +1014,8 @@ public final class BroadcastQueue { // significant amounts of time. int numReceivers = (r.receivers != null) ? r.receivers.size() : 0; if (mService.mProcessesReady && r.dispatchTime > 0) { - long now = SystemClock.uptimeMillis(); if ((numReceivers > 0) && - (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) { + (now > r.dispatchTime + (2 * mConstants.TIMEOUT * numReceivers))) { Slog.w(TAG, "Hung broadcast [" + mQueueName + "] discarded after timeout failure:" + " now=" + now @@ -993,27 +1039,53 @@ public final class BroadcastQueue { return; } + // Is the current broadcast is done for any reason? if (r.receivers == null || r.nextReceiver >= numReceivers || r.resultAbort || forceReceive) { - // No more receivers for this broadcast! Send the final - // result if requested... + // Send the final result if requested if (r.resultTo != null) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST, - "Finishing broadcast [" + mQueueName + "] " - + r.intent.getAction() + " app=" + r.callerApp); - performReceiveLocked(r.callerApp, r.resultTo, - new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false, false, r.userId); - // Set this to null so that the reference - // (local and remote) isn't kept in the mBroadcastHistory. - r.resultTo = null; - } catch (RemoteException e) { - r.resultTo = null; - Slog.w(TAG, "Failure [" - + mQueueName + "] sending broadcast result of " - + r.intent, e); - + boolean sendResult = true; + + // if this was part of a split/deferral complex, update the refcount and only + // send the completion when we clear all of them + if (r.splitToken != 0) { + int newCount = mSplitRefcounts.get(r.splitToken) - 1; + if (newCount == 0) { + // done! clear out this record's bookkeeping and deliver + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Sending broadcast completion for split token " + + r.splitToken); + } + mSplitRefcounts.delete(r.splitToken); + } else { + // still have some split broadcast records in flight; update refcount + // and hold off on the callback + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Result refcount " + newCount + " for split token " + + r.splitToken + " - not sending completion yet"); + } + sendResult = false; + mSplitRefcounts.put(r.splitToken, newCount); + } + } + if (sendResult) { + try { + if (DEBUG_BROADCAST) { + Slog.i(TAG_BROADCAST, "Finishing broadcast [" + mQueueName + "] " + + r.intent.getAction() + " app=" + r.callerApp); + } + performReceiveLocked(r.callerApp, r.resultTo, + new Intent(r.intent), r.resultCode, + r.resultData, r.resultExtras, false, false, r.userId); + // Set this to null so that the reference + // (local and remote) isn't kept in the mBroadcastHistory. + r.resultTo = null; + } catch (RemoteException e) { + r.resultTo = null; + Slog.w(TAG, "Failure [" + + mQueueName + "] sending broadcast result of " + + r.intent, e); + } } } @@ -1031,11 +1103,76 @@ public final class BroadcastQueue { mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage, r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime); } - mOrderedBroadcasts.remove(0); + mDispatcher.retireBroadcastLocked(r); r = null; looped = true; continue; } + + // Check whether the next receiver is under deferral policy, and handle that + // accordingly. If the current broadcast was already part of deferred-delivery + // tracking, we know that it must now be deliverable as-is without re-deferral. + if (!r.deferred) { + final int receiverUid = r.getReceiverUid(r.receivers.get(r.nextReceiver)); + if (mDispatcher.isDeferringLocked(receiverUid)) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG_BROADCAST, "Next receiver in " + r + " uid " + receiverUid + + " at " + r.nextReceiver + " is under deferral"); + } + // If this is the only (remaining) receiver in the broadcast, "splitting" + // doesn't make sense -- just defer it as-is and retire it as the + // currently active outgoing broadcast. + BroadcastRecord defer; + if (r.nextReceiver + 1 == numReceivers) { + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG, "Sole receiver of " + r + + " is under deferral; setting aside and proceeding"); + } + defer = r; + mDispatcher.retireBroadcastLocked(r); + } else { + // Nontrivial case; split out 'uid's receivers to a new broadcast record + // and defer that, then loop and pick up continuing delivery of the current + // record (now absent those receivers). + + // The split operation is guaranteed to match at least at 'nextReceiver' + defer = r.splitRecipientsLocked(receiverUid, r.nextReceiver); + if (DEBUG_BROADCAST_DEFERRAL) { + Slog.i(TAG_BROADCAST, "Post split:"); + Slog.i(TAG_BROADCAST, "Original broadcast receivers:"); + for (int i = 0; i < r.receivers.size(); i++) { + Slog.i(TAG_BROADCAST, " " + r.receivers.get(i)); + } + Slog.i(TAG_BROADCAST, "Split receivers:"); + for (int i = 0; i < defer.receivers.size(); i++) { + Slog.i(TAG_BROADCAST, " " + defer.receivers.get(i)); + } + } + // Track completion refcount as well if relevant + if (r.resultTo != null) { + int token = r.splitToken; + if (token == 0) { + // first split of this record; refcount for 'r' and 'deferred' + r.splitToken = defer.splitToken = nextSplitTokenLocked(); + mSplitRefcounts.put(r.splitToken, 2); + } else { + // new split from an already-refcounted situation; increment count + final int curCount = mSplitRefcounts.get(token); + if (DEBUG_BROADCAST_DEFERRAL) { + if (curCount == 0) { + Slog.wtf(TAG, "Split refcount is zero with token for " + r); + } + } + mSplitRefcounts.put(token, curCount + 1); + } + } + } + mDispatcher.addDeferredBroadcast(receiverUid, defer); + r = null; + looped = true; + continue; + } + } } while (r == null); // Get the next receiver... @@ -1066,7 +1203,7 @@ public final class BroadcastQueue { + mQueueName + "] " + r); } if (! mPendingBroadcastTimeoutMessage) { - long timeoutTime = r.receiverTime + mTimeoutPeriod; + long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Submitting BROADCAST_TIMEOUT_MSG [" + mQueueName + "] for " + r + " at " + timeoutTime); @@ -1474,12 +1611,12 @@ public final class BroadcastQueue { mPendingBroadcastTimeoutMessage = false; } - if (mOrderedBroadcasts.size() == 0) { + if (mDispatcher.isEmpty() || mDispatcher.getActiveBroadcastLocked() == null) { return; } long now = SystemClock.uptimeMillis(); - BroadcastRecord r = mOrderedBroadcasts.get(0); + BroadcastRecord r = mDispatcher.getActiveBroadcastLocked(); if (fromMsg) { if (!mService.mProcessesReady) { // Only process broadcast timeouts if the system is ready. That way @@ -1488,7 +1625,7 @@ public final class BroadcastQueue { return; } - long timeoutTime = r.receiverTime + mTimeoutPeriod; + long timeoutTime = r.receiverTime + mConstants.TIMEOUT; if (timeoutTime > now) { // We can observe premature timeouts because we do not cancel and reset the // broadcast timeout message after each receiver finishes. Instead, we set up @@ -1503,16 +1640,15 @@ public final class BroadcastQueue { } } - BroadcastRecord br = mOrderedBroadcasts.get(0); - if (br.state == BroadcastRecord.WAITING_SERVICES) { + if (r.state == BroadcastRecord.WAITING_SERVICES) { // In this case the broadcast had already finished, but we had decided to wait // for started services to finish as well before going on. So if we have actually // waited long enough time timeout the broadcast, let's give up on the whole thing // and just move on to the next. - Slog.i(TAG, "Waited long enough for: " + (br.curComponent != null - ? br.curComponent.flattenToShortString() : "(null)")); - br.curComponent = null; - br.state = BroadcastRecord.IDLE; + Slog.i(TAG, "Waited long enough for: " + (r.curComponent != null + ? r.curComponent.flattenToShortString() : "(null)")); + r.curComponent = null; + r.state = BroadcastRecord.IDLE; processNextBroadcast(false); return; } @@ -1619,13 +1755,8 @@ public final class BroadcastQueue { } } - for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) { - didSomething |= mOrderedBroadcasts.get(i).cleanupDisabledPackageReceiversLocked( - packageName, filterByClasses, userId, doit); - if (!doit && didSomething) { - return true; - } - } + didSomething |= mDispatcher.cleanupDisabledPackageReceiversLocked(packageName, + filterByClasses, userId, doit); return didSomething; } @@ -1665,7 +1796,7 @@ public final class BroadcastQueue { } final boolean isIdle() { - return mParallelBroadcasts.isEmpty() && mOrderedBroadcasts.isEmpty() + return mParallelBroadcasts.isEmpty() && mDispatcher.isEmpty() && (mPendingBroadcast == null); } @@ -1677,10 +1808,7 @@ public final class BroadcastQueue { for (int i = N - 1; i >= 0; i--) { mParallelBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.PARALLEL_BROADCASTS); } - N = mOrderedBroadcasts.size(); - for (int i = N - 1; i >= 0; i--) { - mOrderedBroadcasts.get(i).writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS); - } + mDispatcher.writeToProto(proto, BroadcastQueueProto.ORDERED_BROADCASTS); if (mPendingBroadcast != null) { mPendingBroadcast.writeToProto(proto, BroadcastQueueProto.PENDING_BROADCAST); } @@ -1721,7 +1849,7 @@ public final class BroadcastQueue { final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage, boolean needSep) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0 + if (!mParallelBroadcasts.isEmpty() || !mDispatcher.isEmpty() || mPendingBroadcast != null) { boolean printed = false; for (int i = mParallelBroadcasts.size() - 1; i >= 0; i--) { @@ -1740,29 +1868,12 @@ public final class BroadcastQueue { pw.println(" Active Broadcast " + mQueueName + " #" + i + ":"); br.dump(pw, " ", sdf); } - printed = false; - needSep = true; - for (int i = mOrderedBroadcasts.size() - 1; i >= 0; i--) { - BroadcastRecord br = mOrderedBroadcasts.get(i); - if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) { - continue; - } - if (!printed) { - if (needSep) { - pw.println(); - } - needSep = true; - printed = true; - pw.println(" Active ordered broadcasts [" + mQueueName + "]:"); - } - pw.println(" Active Ordered Broadcast " + mQueueName + " #" + i + ":"); - mOrderedBroadcasts.get(i).dump(pw, " ", sdf); - } + + mDispatcher.dumpLocked(pw, dumpPackage, mQueueName, sdf); + if (dumpPackage == null || (mPendingBroadcast != null && dumpPackage.equals(mPendingBroadcast.callerPackage))) { - if (needSep) { - pw.println(); - } + pw.println(); pw.println(" Pending broadcast [" + mQueueName + "]:"); if (mPendingBroadcast != null) { mPendingBroadcast.dump(pw, " ", sdf); @@ -1773,6 +1884,8 @@ public final class BroadcastQueue { } } + mConstants.dump(pw); + int i; boolean printed = false; diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index 9e799f6f14f3..d9e03f8f908f 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -36,10 +36,12 @@ import com.android.internal.annotations.VisibleForTesting; import java.io.PrintWriter; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * An active intent broadcast. @@ -64,6 +66,9 @@ final class BroadcastRecord extends Binder { final int[] delivery; // delivery state of each receiver final long[] duration; // duration a receiver took to process broadcast IIntentReceiver resultTo; // who receives final result if non-null + boolean deferred; + int splitCount; // refcount for result callback, when split + int splitToken; // identifier for cross-BroadcastRecord refcount long enqueueClockTime; // the clock time the broadcast was enqueued long dispatchTime; // when dispatch started on this set of receivers long dispatchClockTime; // the clock time the dispatch started @@ -106,6 +111,9 @@ final class BroadcastRecord extends Binder { ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // info about the receiver that is currently running. + // Private refcount-management bookkeeping; start > 0 + static AtomicInteger sNextToken = new AtomicInteger(1); + void dump(PrintWriter pw, String prefix, SimpleDateFormat sdf) { final long now = SystemClock.uptimeMillis(); @@ -304,6 +312,52 @@ final class BroadcastRecord extends Binder { allowBackgroundActivityStarts = from.allowBackgroundActivityStarts; } + /** + * Split off a new BroadcastRecord that clones this one, but contains only the + * recipient records for the current (just-finished) receiver's app, starting + * after the just-finished receiver [i.e. at r.nextReceiver]. Returns null + * if there are no matching subsequent receivers in this BroadcastRecord. + */ + BroadcastRecord splitRecipientsLocked(int slowAppUid, int startingAt) { + // Do we actually have any matching receivers down the line...? + ArrayList splitReceivers = null; + for (int i = startingAt; i < receivers.size(); ) { + Object o = receivers.get(i); + if (getReceiverUid(o) == slowAppUid) { + if (splitReceivers == null) { + splitReceivers = new ArrayList<>(); + } + splitReceivers.add(o); + receivers.remove(i); + break; + } else { + i++; + } + } + + // No later receivers in the same app, so we have no more to do + if (splitReceivers == null) { + return null; + } + + // build a new BroadcastRecord around that single-target list + BroadcastRecord split = new BroadcastRecord(queue, intent, callerApp, + callerPackage, callingPid, callingUid, callerInstantApp, resolvedType, + requiredPermissions, appOp, options, splitReceivers, resultTo, resultCode, + resultData, resultExtras, ordered, sticky, initialSticky, userId, + allowBackgroundActivityStarts); + + return split; + } + + int getReceiverUid(Object receiver) { + if (receiver instanceof BroadcastFilter) { + return ((BroadcastFilter) receiver).owningUid; + } else /* if (receiver instanceof ResolveInfo) */ { + return ((ResolveInfo) receiver).activityInfo.applicationInfo.uid; + } + } + public BroadcastRecord maybeStripForHistory() { if (!intent.canStripForHistory()) { return this; diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index b381cbfbd0a9..0b3bd70ba5c9 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -79,6 +79,7 @@ android:singleUser="true" android:exported="true" /> <receiver android:name="TrackTimeReceiver" /> <receiver android:name="AlarmSpamReceiver" /> + <receiver android:name="SlowReceiver" /> <activity android:name="DisableScreenshotsActivity" android:label="DisableScreenshots" android:theme="@style/DisableScreenshots"> 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 0f4960887a33..2581e083cc64 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -16,21 +16,21 @@ package com.google.android.test.activity; -import java.util.ArrayList; -import java.util.List; - import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProviderClient; +import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.UserInfo; +import android.content.res.Configuration; +import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; @@ -42,21 +42,18 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.graphics.Bitmap; import android.provider.Settings; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.ScrollView; -import android.widget.Toast; +import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.content.Context; -import android.content.pm.UserInfo; -import android.content.res.Configuration; -import android.util.Log; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; +import java.util.ArrayList; +import java.util.List; public class ActivityTestMain extends Activity { static final String TAG = "ActivityTest"; @@ -73,8 +70,13 @@ public class ActivityTestMain extends Activity { ServiceConnection mIsolatedConnection; + static final String SLOW_RECEIVER_ACTION = "com.google.android.test.activity.SLOW_ACTION"; + static final String SLOW_RECEIVER_EXTRA = "slow_ordinal"; + static final int MSG_SPAM = 1; static final int MSG_SPAM_ALARM = 2; + static final int MSG_SLOW_RECEIVER = 3; + static final int MSG_SLOW_ALARM_RECEIVER = 4; final Handler mHandler = new Handler() { @Override @@ -100,11 +102,58 @@ public class ActivityTestMain extends Activity { mAlarm.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, when+(30*1000), pi); scheduleSpamAlarm(30*1000); } break; + case MSG_SLOW_RECEIVER: { + // Several back to back, to illustrate dispatch policy + Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class); + intent.setAction(SLOW_RECEIVER_ACTION); + intent.putExtra(SLOW_RECEIVER_EXTRA, 1); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 2); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 3); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + } break; + case MSG_SLOW_ALARM_RECEIVER: { + // Several back to back, to illustrate dispatch policy + Intent intent = new Intent(ActivityTestMain.this, SlowReceiver.class); + intent.setAction(SLOW_RECEIVER_ACTION); + intent.putExtra(SLOW_RECEIVER_EXTRA, 1); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 2); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + intent.putExtra(SLOW_RECEIVER_EXTRA, 3); + sendOrderedBroadcast(intent, null, mSlowReceiverCompletion, mHandler, + Activity.RESULT_OK, null, null); + + // Also send a broadcast alarm to evaluate the alarm fast-forward policy + intent.putExtra(SLOW_RECEIVER_EXTRA, 4); + PendingIntent pi = PendingIntent.getBroadcast(ActivityTestMain.this, 1, intent, 0); + AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + long now = SystemClock.elapsedRealtime(); + Log.i(TAG, "Setting alarm for now + 5 seconds"); + am.setExact(AlarmManager.ELAPSED_REALTIME, now + 5_000, pi); + } break; } super.handleMessage(msg); } }; + final BroadcastReceiver mSlowReceiverCompletion = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int extra = intent.getIntExtra(SLOW_RECEIVER_EXTRA, -1); + final String msg = "Slow receiver " + extra + " completed"; + Toast.makeText(ActivityTestMain.this, msg, Toast.LENGTH_LONG) + .show(); + Log.i(TAG, msg); + } + }; + class BroadcastResultReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -387,6 +436,18 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Slow receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + scheduleSlowReceiver(); + return true; + } + }); + menu.add("Slow alarm receiver").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + scheduleSlowAlarmReceiver(); + return true; + } + }); menu.add("Spam!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { scheduleSpam(false); @@ -469,6 +530,7 @@ public class ActivityTestMain extends Activity { protected void onStop() { super.onStop(); mHandler.removeMessages(MSG_SPAM_ALARM); + mHandler.removeMessages(MSG_SLOW_RECEIVER); for (ServiceConnection conn : mConnections) { unbindService(conn); } @@ -544,6 +606,16 @@ public class ActivityTestMain extends Activity { mHandler.sendMessageDelayed(msg, delay); } + void scheduleSlowReceiver() { + mHandler.removeMessages(MSG_SLOW_RECEIVER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_RECEIVER), 500); + } + + void scheduleSlowAlarmReceiver() { + mHandler.removeMessages(MSG_SLOW_ALARM_RECEIVER); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SLOW_ALARM_RECEIVER), 500); + } + private View scrollWrap(View view) { ScrollView scroller = new ScrollView(this); scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java new file mode 100644 index 000000000000..0437a289741c --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/SlowReceiver.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 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 com.google.android.test.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; +import android.util.Log; + +public class SlowReceiver extends BroadcastReceiver { + private static final String TAG = "SlowReceiver"; + private static final long RECEIVER_DELAY = 6_000; + + @Override + public void onReceive(Context context, Intent intent) { + final int extra = intent.getIntExtra(ActivityTestMain.SLOW_RECEIVER_EXTRA, -1); + if (extra == 1) { + Log.i(TAG, "Received broadcast 1; delaying return by " + RECEIVER_DELAY + " ms"); + long now = SystemClock.elapsedRealtime(); + final long end = now + RECEIVER_DELAY; + while (now < end) { + try { + Thread.sleep(end - now); + } catch (InterruptedException e) { } + now = SystemClock.elapsedRealtime(); + } + } else { + Log.i(TAG, "Extra parameter not 1, returning immediately"); + } + Log.i(TAG, "Returning from onReceive()"); + } +} |