summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Dianne Hackborn <hackbod@google.com> 2017-06-15 18:45:28 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2017-06-15 18:45:35 +0000
commitef0554438f11c9ea5d7bcd5bab5c97808b55dd43 (patch)
tree2003065bb95d62df343ff80c869981ee4a1db947
parent04298759fe956ce712a84d7df049e14c1815cb07 (diff)
parentcb01563d7efa5689abb98fe4e5d8cc86bfc2b6fc (diff)
Merge "Work on issue #36891897: Need to ensure foreground services..." into oc-dev
-rw-r--r--core/java/android/os/Handler.java20
-rw-r--r--core/java/android/os/Looper.java15
-rw-r--r--core/java/android/os/MessageQueue.java23
-rwxr-xr-xcore/java/android/provider/Settings.java5
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java144
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java57
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerDebugConfig.java1
7 files changed, 216 insertions, 49 deletions
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 8678d95db17d..b69a23aa5854 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -696,6 +696,14 @@ public class Handler {
}
/**
+ * Return whether there are any messages or callbacks currently scheduled on this handler.
+ * @hide
+ */
+ public final boolean hasMessagesOrCallbacks() {
+ return mQueue.hasMessages(this);
+ }
+
+ /**
* Check if there are any pending posts of messages with code 'what' and
* whose obj is 'object' in the message queue.
*/
@@ -728,6 +736,18 @@ public class Handler {
}
}
+ /**
+ * @hide
+ */
+ public final void dumpMine(Printer pw, String prefix) {
+ pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
+ if (mLooper == null) {
+ pw.println(prefix + "looper uninitialized");
+ } else {
+ mLooper.dump(pw, prefix + " ", this);
+ }
+ }
+
@Override
public String toString() {
return "Handler (" + getClass().getName() + ") {"
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 44dbcfb09582..04cceb8e80ba 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -310,7 +310,20 @@ public final class Looper {
*/
public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
- mQueue.dump(pw, prefix + " ");
+ mQueue.dump(pw, prefix + " ", null);
+ }
+
+ /**
+ * Dumps the state of the looper for debugging purposes.
+ *
+ * @param pw A printer to receive the contents of the dump.
+ * @param prefix A prefix to prepend to each line which is printed.
+ * @param handler Only dump messages for this Handler.
+ * @hide
+ */
+ public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
+ pw.println(prefix + toString());
+ mQueue.dump(pw, prefix + " ", handler);
}
/** @hide */
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java
index 2a8c52e92c60..624e28a67ae6 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/MessageQueue.java
@@ -620,6 +620,23 @@ public final class MessageQueue {
}
}
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+
+ synchronized (this) {
+ Message p = mMessages;
+ while (p != null) {
+ if (p.target == h) {
+ return true;
+ }
+ p = p.next;
+ }
+ return false;
+ }
+ }
+
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
@@ -759,12 +776,14 @@ public final class MessageQueue {
}
}
- void dump(Printer pw, String prefix) {
+ void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
- pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
n++;
}
pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f1ce9d52e920..65669ee70216 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9029,7 +9029,10 @@ public final class Settings {
* <pre>
* max_cached_processes (int)
* background_settle_time (long)
- * foreground_service_ui_min_time (long)
+ * fgservice_min_shown_time (long)
+ * fgservice_min_report_time (long)
+ * fgservice_screen_on_before_time (long)
+ * fgservice_screen_on_after_time (long)
* content_provider_retain_time (long)
* gc_timeout (long)
* gc_min_interval (long)
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index c41748424b10..de144777e7c4 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -81,6 +81,7 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.EventLog;
+import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -175,6 +176,9 @@ public final class ActiveServices {
long mStartVisibleTime;
long mEndTime;
int mNumActive;
+
+ // Temp output of foregroundAppShownEnoughLocked
+ long mHideTime;
}
/**
@@ -736,50 +740,90 @@ public final class ActiveServices {
}
}
+ boolean foregroundAppShownEnoughLocked(ActiveForegroundApp aa, long nowElapsed) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Shown enough: pkg=" + aa.mPackageName + ", uid="
+ + aa.mUid);
+ boolean canRemove = false;
+ aa.mHideTime = Long.MAX_VALUE;
+ if (aa.mShownWhileTop) {
+ // If the app was ever at the top of the screen while the foreground
+ // service was running, then we can always just immediately remove it.
+ canRemove = true;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown while on top");
+ } else if (mScreenOn || aa.mShownWhileScreenOn) {
+ final long minTime = aa.mStartVisibleTime
+ + (aa.mStartTime != aa.mStartVisibleTime
+ ? mAm.mConstants.FGSERVICE_SCREEN_ON_AFTER_TIME
+ : mAm.mConstants.FGSERVICE_MIN_SHOWN_TIME);
+ if (nowElapsed >= minTime) {
+ // If shown while the screen is on, and it has been shown for
+ // at least the minimum show time, then we can now remove it.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - shown long enough with screen on");
+ canRemove = true;
+ } else {
+ // This is when we will be okay to stop telling the user.
+ long reportTime = nowElapsed + mAm.mConstants.FGSERVICE_MIN_REPORT_TIME;
+ aa.mHideTime = reportTime > minTime ? reportTime : minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen on");
+ }
+ } else {
+ final long minTime = aa.mEndTime
+ + mAm.mConstants.FGSERVICE_SCREEN_ON_BEFORE_TIME;
+ if (nowElapsed >= minTime) {
+ // If the foreground service has only run while the screen is
+ // off, but it has been gone now for long enough that we won't
+ // care to tell the user about it when the screen comes back on,
+ // then we can remove it now.
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "YES - gone long enough with screen off");
+ canRemove = true;
+ } else {
+ // This is when we won't care about this old fg service.
+ aa.mHideTime = minTime;
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "NO -- wait " + (aa.mHideTime-nowElapsed)
+ + " with screen off");
+ }
+ }
+ return canRemove;
+ }
+
void updateForegroundApps(ServiceMap smap) {
// This is called from the handler without the lock held.
ArrayList<ActiveForegroundApp> active = null;
synchronized (mAm) {
final long now = SystemClock.elapsedRealtime();
- final long nowPlusMin = now + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
long nextUpdateTime = Long.MAX_VALUE;
if (smap != null) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Updating foreground apps for user "
+ + smap.mUserId);
for (int i = smap.mActiveForegroundApps.size()-1; i >= 0; i--) {
ActiveForegroundApp aa = smap.mActiveForegroundApps.valueAt(i);
- if (aa.mEndTime != 0 && (mScreenOn || aa.mShownWhileScreenOn)) {
- if (!aa.mShownWhileTop && aa.mEndTime < (aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
- // Check to see if this should still be displayed... we continue
- // until it has been shown for at least the timeout duration.
- if (nowPlusMin >= aa.mStartVisibleTime) {
- // All over!
- smap.mActiveForegroundApps.removeAt(i);
- smap.mActiveForegroundAppsChanged = true;
- continue;
- } else {
- long hideTime = aa.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME;
- if (hideTime < nextUpdateTime) {
- nextUpdateTime = hideTime;
- }
- }
- } else {
+ if (aa.mEndTime != 0) {
+ boolean canRemove = foregroundAppShownEnoughLocked(aa, now);
+ if (canRemove) {
// This was up for longer than the timeout, so just remove immediately.
smap.mActiveForegroundApps.removeAt(i);
smap.mActiveForegroundAppsChanged = true;
continue;
}
+ if (aa.mHideTime < nextUpdateTime) {
+ nextUpdateTime = aa.mHideTime;
+ }
}
if (!aa.mAppOnTop) {
if (active == null) {
active = new ArrayList<>();
}
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Adding active: pkg="
+ + aa.mPackageName + ", uid=" + aa.mUid);
active.add(aa);
}
}
smap.removeMessages(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
if (nextUpdateTime < Long.MAX_VALUE) {
- Message msg = smap.obtainMessage();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Next update time in: "
+ + (nextUpdateTime-now));
+ Message msg = smap.obtainMessage(ServiceMap.MSG_UPDATE_FOREGROUND_APPS);
smap.sendMessageAtTime(msg, nextUpdateTime
+ SystemClock.uptimeMillis() - SystemClock.elapsedRealtime());
}
@@ -882,15 +926,14 @@ public final class ActiveServices {
active.mNumActive--;
if (active.mNumActive <= 0) {
active.mEndTime = SystemClock.elapsedRealtime();
- if (active.mShownWhileTop || active.mEndTime >= (active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME)) {
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Ended running of service");
+ if (foregroundAppShownEnoughLocked(active, active.mEndTime)) {
// Have been active for long enough that we will remove it immediately.
smap.mActiveForegroundApps.remove(r.packageName);
smap.mActiveForegroundAppsChanged = true;
requestUpdateActiveForegroundAppsLocked(smap, 0);
- } else {
- requestUpdateActiveForegroundAppsLocked(smap, active.mStartVisibleTime
- + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
+ } else if (active.mHideTime < Long.MAX_VALUE){
+ requestUpdateActiveForegroundAppsLocked(smap, active.mHideTime);
}
}
}
@@ -904,26 +947,44 @@ public final class ActiveServices {
// services that were started while the screen was off.
if (screenOn) {
final long nowElapsed = SystemClock.elapsedRealtime();
+ if (DEBUG_FOREGROUND_SERVICE) Slog.d(TAG, "Screen turned on");
for (int i = mServiceMap.size()-1; i >= 0; i--) {
ServiceMap smap = mServiceMap.valueAt(i);
+ long nextUpdateTime = Long.MAX_VALUE;
boolean changed = false;
for (int j = smap.mActiveForegroundApps.size()-1; j >= 0; j--) {
ActiveForegroundApp active = smap.mActiveForegroundApps.valueAt(j);
- if (!active.mShownWhileScreenOn) {
- changed = true;
- active.mShownWhileScreenOn = mScreenOn;
- active.mStartVisibleTime = nowElapsed;
- if (active.mEndTime != 0) {
- active.mEndTime = nowElapsed;
+ if (active.mEndTime == 0) {
+ if (!active.mShownWhileScreenOn) {
+ active.mShownWhileScreenOn = true;
+ active.mStartVisibleTime = nowElapsed;
+ }
+ } else {
+ if (!active.mShownWhileScreenOn
+ && active.mStartVisibleTime == active.mStartTime) {
+ // If this was never shown while the screen was on, then we will
+ // count the time it started being visible as now, to tell the user
+ // about it now that they have a screen to look at.
+ active.mEndTime = active.mStartVisibleTime = nowElapsed;
+ }
+ if (foregroundAppShownEnoughLocked(active, nowElapsed)) {
+ // Have been active for long enough that we will remove it
+ // immediately.
+ smap.mActiveForegroundApps.remove(active.mPackageName);
+ smap.mActiveForegroundAppsChanged = true;
+ changed = true;
+ } else {
+ if (active.mHideTime < nextUpdateTime) {
+ nextUpdateTime = active.mHideTime;
+ }
}
}
}
if (changed) {
- requestUpdateActiveForegroundAppsLocked(smap,
- nowElapsed + mAm.mConstants.FOREGROUND_SERVICE_UI_MIN_TIME);
- } else if (smap.mActiveForegroundApps.size() > 0) {
- // Just being paranoid.
+ // Need to immediately update.
requestUpdateActiveForegroundAppsLocked(smap, 0);
+ } else if (nextUpdateTime < Long.MAX_VALUE) {
+ requestUpdateActiveForegroundAppsLocked(smap, nextUpdateTime);
}
}
}
@@ -2318,7 +2379,7 @@ public final class ActiveServices {
return true;
}
- // Is someone still bound to us keepign us running?
+ // Is someone still bound to us keeping us running?
if (!knowConn) {
hasConn = r.hasAutoCreateConnections();
}
@@ -3741,6 +3802,17 @@ public final class ActiveServices {
pw.println();
}
}
+ if (smap.hasMessagesOrCallbacks()) {
+ if (needSep) {
+ pw.println();
+ }
+ printedAnything = true;
+ needSep = true;
+ pw.print(" Handler - user ");
+ pw.print(user);
+ pw.println(":");
+ smap.dumpMine(new PrintWriterPrinter(pw), " ");
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 5749f31ddb2e..6c3fe915f386 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -35,8 +35,14 @@ final class ActivityManagerConstants extends ContentObserver {
// Key names stored in the settings value.
private static final String KEY_MAX_CACHED_PROCESSES = "max_cached_processes";
private static final String KEY_BACKGROUND_SETTLE_TIME = "background_settle_time";
- private static final String KEY_FOREGROUND_SERVICE_UI_MIN_TIME
- = "foreground_service_ui_min_time";
+ private static final String KEY_FGSERVICE_MIN_SHOWN_TIME
+ = "fgservice_min_shown_time";
+ private static final String KEY_FGSERVICE_MIN_REPORT_TIME
+ = "fgservice_min_report_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME
+ = "fgservice_screen_on_before_time";
+ private static final String KEY_FGSERVICE_SCREEN_ON_AFTER_TIME
+ = "fgservice_screen_on_after_time";
private static final String KEY_CONTENT_PROVIDER_RETAIN_TIME = "content_provider_retain_time";
private static final String KEY_GC_TIMEOUT = "gc_timeout";
private static final String KEY_GC_MIN_INTERVAL = "gc_min_interval";
@@ -58,7 +64,10 @@ final class ActivityManagerConstants extends ContentObserver {
private static final int DEFAULT_MAX_CACHED_PROCESSES = 32;
private static final long DEFAULT_BACKGROUND_SETTLE_TIME = 60*1000;
- private static final long DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME = 30*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_SHOWN_TIME = 2*1000;
+ private static final long DEFAULT_FGSERVICE_MIN_REPORT_TIME = 3*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME = 1*1000;
+ private static final long DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME = 5*1000;
private static final long DEFAULT_CONTENT_PROVIDER_RETAIN_TIME = 20*1000;
private static final long DEFAULT_GC_TIMEOUT = 5*1000;
private static final long DEFAULT_GC_MIN_INTERVAL = 60*1000;
@@ -85,8 +94,26 @@ final class ActivityManagerConstants extends ContentObserver {
// before we start restricting what it can do.
public long BACKGROUND_SETTLE_TIME = DEFAULT_BACKGROUND_SETTLE_TIME;
- // The minimum time a foreground service will be shown as running in the notification UI.
- public long FOREGROUND_SERVICE_UI_MIN_TIME = DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME;
+ // The minimum time we allow a foreground service to run with a notification and the
+ // screen on without otherwise telling the user about it. (If it runs for less than this,
+ // it will still be reported to the user as a running app for at least this amount of time.)
+ public long FGSERVICE_MIN_SHOWN_TIME = DEFAULT_FGSERVICE_MIN_SHOWN_TIME;
+
+ // If a foreground service is shown for less than FGSERVICE_MIN_SHOWN_TIME, we will display
+ // the background app running notification about it for at least this amount of time (if it
+ // is larger than the remaining shown time).
+ public long FGSERVICE_MIN_REPORT_TIME = DEFAULT_FGSERVICE_MIN_REPORT_TIME;
+
+ // The minimum amount of time the foreground service needs to have remain being shown
+ // before the screen goes on for us to consider it not worth showing to the user. That is
+ // if an app has a foreground service that stops itself this amount of time or more before
+ // the user turns on the screen, we will just let it go without the user being told about it.
+ public long FGSERVICE_SCREEN_ON_BEFORE_TIME = DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME;
+
+ // The minimum amount of time a foreground service should remain reported to the user if
+ // it is stopped when the screen turns on. This is the time from when the screen turns
+ // on until we will stop reporting it.
+ public long FGSERVICE_SCREEN_ON_AFTER_TIME = DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME;
// How long we will retain processes hosting content providers in the "last activity"
// state before allowing them to drop down to the regular cached LRU list. This is
@@ -225,8 +252,14 @@ final class ActivityManagerConstants extends ContentObserver {
DEFAULT_MAX_CACHED_PROCESSES);
BACKGROUND_SETTLE_TIME = mParser.getLong(KEY_BACKGROUND_SETTLE_TIME,
DEFAULT_BACKGROUND_SETTLE_TIME);
- FOREGROUND_SERVICE_UI_MIN_TIME = mParser.getLong(KEY_FOREGROUND_SERVICE_UI_MIN_TIME,
- DEFAULT_FOREGROUND_SERVICE_UI_MIN_TIME);
+ FGSERVICE_MIN_SHOWN_TIME = mParser.getLong(KEY_FGSERVICE_MIN_SHOWN_TIME,
+ DEFAULT_FGSERVICE_MIN_SHOWN_TIME);
+ FGSERVICE_MIN_REPORT_TIME = mParser.getLong(KEY_FGSERVICE_MIN_REPORT_TIME,
+ DEFAULT_FGSERVICE_MIN_REPORT_TIME);
+ FGSERVICE_SCREEN_ON_BEFORE_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ FGSERVICE_SCREEN_ON_AFTER_TIME = mParser.getLong(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME,
+ DEFAULT_FGSERVICE_SCREEN_ON_AFTER_TIME);
CONTENT_PROVIDER_RETAIN_TIME = mParser.getLong(KEY_CONTENT_PROVIDER_RETAIN_TIME,
DEFAULT_CONTENT_PROVIDER_RETAIN_TIME);
GC_TIMEOUT = mParser.getLong(KEY_GC_TIMEOUT,
@@ -284,8 +317,14 @@ final class ActivityManagerConstants extends ContentObserver {
pw.println(MAX_CACHED_PROCESSES);
pw.print(" "); pw.print(KEY_BACKGROUND_SETTLE_TIME); pw.print("=");
pw.println(BACKGROUND_SETTLE_TIME);
- pw.print(" "); pw.print(KEY_FOREGROUND_SERVICE_UI_MIN_TIME); pw.print("=");
- pw.println(FOREGROUND_SERVICE_UI_MIN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_SHOWN_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_SHOWN_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_MIN_REPORT_TIME); pw.print("=");
+ pw.println(FGSERVICE_MIN_REPORT_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_BEFORE_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_BEFORE_TIME);
+ pw.print(" "); pw.print(KEY_FGSERVICE_SCREEN_ON_AFTER_TIME); pw.print("=");
+ pw.println(FGSERVICE_SCREEN_ON_AFTER_TIME);
pw.print(" "); pw.print(KEY_CONTENT_PROVIDER_RETAIN_TIME); pw.print("=");
pw.println(CONTENT_PROVIDER_RETAIN_TIME);
pw.print(" "); pw.print(KEY_GC_TIMEOUT); pw.print("=");
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index ff5efde27f93..f440100b7621 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -79,6 +79,7 @@ class ActivityManagerDebugConfig {
static final boolean DEBUG_SAVED_STATE = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SCREENSHOTS = DEBUG_ALL_ACTIVITIES || false;
static final boolean DEBUG_SERVICE = DEBUG_ALL || false;
+ static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false;
static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false;
static final boolean DEBUG_STACK = DEBUG_ALL || false;
static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false;