diff options
Diffstat (limited to 'core')
36 files changed, 1667 insertions, 1143 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d709deb..676d6d5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -621,9 +621,15 @@ public class ActivityManager { public String longMsg; /** - * Raw data about the crash (typically a stack trace). + * The stack trace where the error originated. May be null. + * @pending */ - public byte[] crashData; + public String stackTrace; + + /** + * to be deprecated: This value will always be null. + */ + public byte[] crashData = null; public ProcessErrorStateInfo() { } @@ -640,8 +646,7 @@ public class ActivityManager { dest.writeString(tag); dest.writeString(shortMsg); dest.writeString(longMsg); - dest.writeInt(crashData == null ? -1 : crashData.length); - dest.writeByteArray(crashData); + dest.writeString(stackTrace); } public void readFromParcel(Parcel source) { @@ -652,13 +657,7 @@ public class ActivityManager { tag = source.readString(); shortMsg = source.readString(); longMsg = source.readString(); - int cdLen = source.readInt(); - if (cdLen == -1) { - crashData = null; - } else { - crashData = new byte[cdLen]; - source.readByteArray(crashData); - } + stackTrace = source.readString(); } public static final Creator<ProcessErrorStateInfo> CREATOR = diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 3b8aee9..a0498aa 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -982,18 +982,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case HANDLE_APPLICATION_ERROR_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder app = data.readStrongBinder(); - int fl = data.readInt(); String tag = data.readString(); - String shortMsg = data.readString(); - String longMsg = data.readString(); - byte[] crashData = data.createByteArray(); - int res = handleApplicationError(app, fl, tag, shortMsg, longMsg, - crashData); + ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data); + handleApplicationError(app, tag, ci); reply.writeNoException(); - reply.writeInt(res); return true; } - + case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int sig = data.readInt(); @@ -2342,27 +2337,21 @@ class ActivityManagerProxy implements IActivityManager /* this base class version is never called */ return true; } - public int handleApplicationError(IBinder app, int flags, - String tag, String shortMsg, String longMsg, - byte[] crashData) throws RemoteException + public void handleApplicationError(IBinder app, String tag, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(app); - data.writeInt(flags); data.writeString(tag); - data.writeString(shortMsg); - data.writeString(longMsg); - data.writeByteArray(crashData); + crashInfo.writeToParcel(data, 0); mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0); reply.readException(); - int res = reply.readInt(); reply.recycle(); data.recycle(); - return res; } - + public void signalPersistentProcesses(int sig) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 1115f92..909620d 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1383,13 +1383,14 @@ public final class ActivityThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, - int configChanges, boolean notResumed) { + int configChanges, boolean notResumed, Configuration config) { ActivityRecord r = new ActivityRecord(); r.token = token; r.pendingResults = pendingResults; r.pendingIntents = pendingNewIntents; r.startsNotResumed = notResumed; + r.createdConfig = config; synchronized (mRelaunchingActivities) { mRelaunchingActivities.add(r); @@ -2511,7 +2512,7 @@ public final class ActivityThread { Activity a = performLaunchActivity(r, customIntent); if (a != null) { - r.createdConfig = new Configuration(a.getResources().getConfiguration()); + r.createdConfig = new Configuration(mConfiguration); handleResumeActivity(r.token, false, r.isForward); if (!r.activity.mFinished && r.startsNotResumed) { @@ -3563,6 +3564,16 @@ public final class ActivityThread { } } + if (tmp.createdConfig != null) { + // If the activity manager is passing us its current config, + // assume that is really what we want regardless of what we + // may have pending. + if (mConfiguration == null + || mConfiguration.diff(tmp.createdConfig) != 0) { + changedConfig = tmp.createdConfig; + } + } + if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity " + tmp.token + ": changedConfig=" + changedConfig); diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index aeae5f9..e89b3ad0 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -19,6 +19,8 @@ package android.app; import android.os.Parcel; import android.os.Parcelable; import android.util.Printer; +import java.io.PrintWriter; +import java.io.StringWriter; /** * Describes an application error. @@ -187,6 +189,31 @@ public class ApplicationErrorReport implements Parcelable { } /** + * Create an instance of CrashInfo initialized from an exception. + */ + public CrashInfo(Throwable tr) { + StringWriter sw = new StringWriter(); + tr.printStackTrace(new PrintWriter(sw)); + stackTrace = sw.toString(); + + // Populate fields with the "root cause" exception + while (tr.getCause() != null) { + tr = tr.getCause(); + String msg = tr.getMessage(); + if (msg != null && msg.length() > 0) { + exceptionMessage = msg; + } + } + + exceptionClassName = tr.getClass().getName(); + StackTraceElement trace = tr.getStackTrace()[0]; + throwFileName = trace.getFileName(); + throwClassName = trace.getClassName(); + throwMethodName = trace.getMethodName(); + throwLineNumber = trace.getLineNumber(); + } + + /** * Create an instance of CrashInfo initialized from a Parcel. */ public CrashInfo(Parcel in) { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index a772a8f..7cba13f 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -140,7 +140,11 @@ public abstract class ApplicationThreadNative extends Binder List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); int configChanges = data.readInt(); boolean notResumed = data.readInt() != 0; - scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed); + Configuration config = null; + if (data.readInt() != 0) { + config = Configuration.CREATOR.createFromParcel(data); + } + scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config); return true; } @@ -491,7 +495,8 @@ class ApplicationThreadProxy implements IApplicationThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, - int configChanges, boolean notResumed) throws RemoteException { + int configChanges, boolean notResumed, Configuration config) + throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); @@ -499,6 +504,12 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeTypedList(pendingNewIntents); data.writeInt(configChanges); data.writeInt(notResumed ? 1 : 0); + if (config != null) { + data.writeInt(1); + config.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl index 8f6b252..804dd61 100644 --- a/core/java/android/app/IActivityController.aidl +++ b/core/java/android/app/IActivityController.aidl @@ -43,8 +43,9 @@ interface IActivityController * normal error recovery (app crash dialog) to occur, false to kill * it immediately. */ - boolean appCrashed(String processName, int pid, String shortMsg, - String longMsg, in byte[] crashData); + boolean appCrashed(String processName, int pid, + String tag, String shortMsg, String longMsg, + long timeMillis, String stackTrace); /** * An application process is not responding. Return 0 to show the "app diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 9f505ac..c890c4c 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -242,11 +242,9 @@ public interface IActivityManager extends IInterface { // Special low-level communication with activity manager. public void startRunning(String pkg, String cls, String action, String data) throws RemoteException; - // Returns 1 if the user wants to debug. - public int handleApplicationError(IBinder app, - int flags, /* 1 == can debug */ - String tag, String shortMsg, String longMsg, - byte[] crashData) throws RemoteException; + + public void handleApplicationError(IBinder app, String tag, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; /* * This will deliver the specified signal to all the persistent processes. Currently only diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 89a52fd..ed810d3 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -56,7 +56,7 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, - boolean notResumed) throws RemoteException; + boolean notResumed, Configuration config) throws RemoteException; void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException; void scheduleDestroyActivity(IBinder token, boolean finished, int configChanges) throws RemoteException; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 5924865..42d87f4 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -370,9 +370,17 @@ public final class BluetoothAdapter { } /** - * Turn on the local Bluetooth adapter. + * Turn on the local Bluetooth adapter—do not use without explicit + * user action to turn on Bluetooth. * <p>This powers on the underlying Bluetooth hardware, and starts all * Bluetooth system services. + * <p class="caution"><strong>Bluetooth should never be enabled without + * direct user consent</strong>. If you want to turn on Bluetooth in order + * to create a wireless connection, you should use the {@link + * #ACTION_REQUEST_ENABLE} Intent, which will raise a dialog that requests + * user permission to turn on Bluetooth. The {@link #enable()} method is + * provided only for applications that include a user interface for changing + * system settings, such as a "power manager" app.</p> * <p>This is an asynchronous call: it will return immediately, and * clients should listen for {@link #ACTION_STATE_CHANGED} * to be notified of subsequent adapter state changes. If this call returns @@ -382,7 +390,8 @@ public final class BluetoothAdapter { * #STATE_ON}. If this call returns false then there was an * immediate problem that will prevent the adapter from being turned on - * such as Airplane mode, or the adapter is already turned on. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission * * @return true to indicate adapter startup has begun, or false on * immediate error @@ -395,9 +404,14 @@ public final class BluetoothAdapter { } /** - * Turn off the local Bluetooth adapter. + * Turn off the local Bluetooth adapter—do not use without explicit + * user action to turn off Bluetooth. * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth * system services, and powers down the underlying Bluetooth hardware. + * <p class="caution"><strong>Bluetooth should never be disbled without + * direct user consent</strong>. The {@link #disable()} method is + * provided only for applications that include a user interface for changing + * system settings, such as a "power manager" app.</p> * <p>This is an asynchronous call: it will return immediately, and * clients should listen for {@link #ACTION_STATE_CHANGED} * to be notified of subsequent adapter state changes. If this call returns @@ -407,7 +421,8 @@ public final class BluetoothAdapter { * #STATE_ON}. If this call returns false then there was an * immediate problem that will prevent the adapter from being turned off - * such as the adapter already being turned off. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission * * @return true to indicate adapter shutdown has begun, or false on * immediate error diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index bc06713..c7fea9e 100644 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -25,10 +25,6 @@ import android.os.Parcelable; * specify the general device type such as a phone, a computer, or * headset, and whether it's capable of services such as audio or telephony. * - * <p>The Bluetooth class is useful as a hint to roughly describe a device (for example to - * show an icon in the UI), but does not reliably describe which Bluetooth - * profiles or services are actually supported by a device. - * * <p>Every Bluetooth class is composed of zero or more service classes, and * exactly one device class. The device class is further broken down into major * and minor device class components. diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java index 091d44e..154c4a6 100644 --- a/core/java/android/content/AbstractThreadedSyncAdapter.java +++ b/core/java/android/content/AbstractThreadedSyncAdapter.java @@ -37,6 +37,12 @@ import java.util.concurrent.atomic.AtomicInteger; * that the sync has been canceled. */ public abstract class AbstractThreadedSyncAdapter { + /** + * Kernel event log tag. Also listed in data/etc/event-log-tags. + * @Deprecated + */ + public static final int LOG_SYNC_DETAILS = 2743; + private final Context mContext; private final AtomicInteger mNumSyncStarts; private final ISyncAdapterImpl mISyncAdapterImpl; @@ -45,9 +51,6 @@ public abstract class AbstractThreadedSyncAdapter { private SyncThread mSyncThread; private final Object mSyncThreadLock = new Object(); - /** Kernel event log tag. Also listed in data/etc/event-log-tags. */ - public static final int LOG_SYNC_DETAILS = 2743; - private static final String TAG = "Sync"; private final boolean mAutoInitialize; /** @@ -136,8 +139,6 @@ public abstract class AbstractThreadedSyncAdapter { private final String mAuthority; private final Account mAccount; private final Bundle mExtras; - private long mInitialTxBytes; - private long mInitialRxBytes; private SyncThread(String name, SyncContext syncContext, String authority, Account account, Bundle extras) { @@ -156,9 +157,6 @@ public abstract class AbstractThreadedSyncAdapter { } SyncResult syncResult = new SyncResult(); - int uid = Process.myUid(); - mInitialTxBytes = TrafficStats.getUidTxBytes(uid); - mInitialRxBytes = TrafficStats.getUidRxBytes(uid); ContentProviderClient provider = null; try { provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority); @@ -175,8 +173,6 @@ public abstract class AbstractThreadedSyncAdapter { if (!isCanceled()) { mSyncContext.onFinished(syncResult); } - onLogSyncDetails(TrafficStats.getUidTxBytes(uid) - mInitialTxBytes, - TrafficStats.getUidRxBytes(uid) - mInitialRxBytes, syncResult); // synchronize so that the assignment will be seen by other threads // that also synchronize accesses to mSyncThread synchronized (mSyncThreadLock) { @@ -211,18 +207,4 @@ public abstract class AbstractThreadedSyncAdapter { */ public abstract void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult); - - /** - * Logs details on the sync. - * Normally this will be overridden by a subclass that will provide - * provider-specific details. - * - * @param bytesSent number of bytes the sync sent over the network - * @param bytesReceived number of bytes the sync received over the network - * @param result The SyncResult object holding info on the sync - * @hide - */ - protected void onLogSyncDetails(long bytesSent, long bytesReceived, SyncResult result) { - EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, ""); - } } diff --git a/core/java/android/content/EventLogTags.logtags b/core/java/android/content/EventLogTags.logtags new file mode 100644 index 0000000..af50a3c --- /dev/null +++ b/core/java/android/content/EventLogTags.logtags @@ -0,0 +1,10 @@ +# See system/core/logcat/event.logtags for a description of the format of this file. + +option java_package android.content + +# --------------------------- +# SyncAdapter.java +# --------------------------- +# What happens in a sync operation (bytes sent and received, and +# operation details) +2743 sync_details (authority|3),(send|1|2),(recv|1|2),(details|3) diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java index af1634e..a5afe87 100644 --- a/core/java/android/content/SyncAdapter.java +++ b/core/java/android/content/SyncAdapter.java @@ -26,8 +26,8 @@ import android.accounts.Account; public abstract class SyncAdapter { private static final String TAG = "SyncAdapter"; - /** Kernel event log tag. Also listed in data/etc/event-log-tags. */ - public static final int LOG_SYNC_DETAILS = 2743; + /** Kernel event log tag. */ + public static final int LOG_SYNC_DETAILS = EventLogTags.SYNC_DETAILS; class Transport extends ISyncAdapter.Stub { public void startSync(ISyncContext syncContext, String authority, Account account, diff --git a/core/java/android/os/HandlerState.java b/core/java/android/os/HandlerState.java deleted file mode 100644 index 0708f7d..0000000 --- a/core/java/android/os/HandlerState.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -/** - * {@hide} - */ -public abstract class HandlerState { - public HandlerState() { - } - - public void enter(Message message) { - } - - public abstract void processMessage(Message message); - - public void exit(Message message) { - } -} diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java deleted file mode 100644 index 9e7902b..0000000 --- a/core/java/android/os/HandlerStateMachine.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.os; - -import android.util.Log; -import android.util.LogPrinter; - -/** - * {@hide} - * - * Implement a state machine where each state is an object, - * HandlerState. Each HandlerState must implement processMessage - * and optionally enter/exit. When a state machine is created - * the initial state must be set. When messages are sent to - * a state machine the current state's processMessage method is - * invoked. If this is the first message for this state the - * enter method is called prior to processMessage and when - * transtionTo is invoked the state's exit method will be - * called after returning from processMessage. - * - * If a message should be handled in a different state the - * processMessage method may call deferMessage. This causes - * the message to be saved on a list until transitioning - * to a new state, at which time all of the deferred messages - * will be put on the front of the state machines queue and - * processed by the new current state's processMessage - * method. - * - * Below is an example state machine with two state's, S1 and S2. - * The initial state is S1 which defers all messages and only - * transition to S2 when message.what == TEST_WHAT_2. State S2 - * will process each messages until it receives TEST_WHAT_2 - * where it will transition back to S1: -<code> - class StateMachine1 extends HandlerStateMachine { - private static final int TEST_WHAT_1 = 1; - private static final int TEST_WHAT_2 = 2; - - StateMachine1(String name) { - super(name); - setInitialState(mS1); - } - - class S1 extends HandlerState { - &#064;Override public void enter(Message message) { - } - - &#064;Override public void processMessage(Message message) { - deferMessage(message); - if (message.what == TEST_WHAT_2) { - transitionTo(mS2); - } - } - - &#064;Override public void exit(Message message) { - } - } - - class S2 extends HandlerState { - &#064;Override public void processMessage(Message message) { - // Do some processing - if (message.what == TEST_WHAT_2) { - transtionTo(mS1); - } - } - } - - private S1 mS1 = new S1(); - private S2 mS2 = new S2(); - } -</code> - */ -public class HandlerStateMachine { - - private boolean mDbg = false; - private static final String TAG = "HandlerStateMachine"; - private String mName; - private SmHandler mHandler; - private HandlerThread mHandlerThread; - - /** - * Handle messages sent to the state machine by calling - * the current state's processMessage. It also handles - * the enter/exit calls and placing any deferred messages - * back onto the queue when transitioning to a new state. - */ - class SmHandler extends Handler { - - SmHandler(Looper looper) { - super(looper); - } - - /** - * This will dispatch the message to the - * current state's processMessage. - */ - @Override - final public void handleMessage(Message msg) { - if (mDbg) Log.d(TAG, "SmHandler.handleMessage E"); - if (mDestState != null) { - if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter"); - mCurrentState = mDestState; - mDestState = null; - mCurrentState.enter(msg); - } - if (mCurrentState != null) { - if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage"); - mCurrentState.processMessage(msg); - } else { - /* Strange no state to execute */ - Log.e(TAG, "handleMessage: no current state, did you call setInitialState"); - } - - if (mDestState != null) { - if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit"); - mCurrentState.exit(msg); - - /** - * Place the messages from the deferred queue:t - * on to the Handler's message queue in the - * same order that they originally arrived. - * - * We set cur.when = 0 to circumvent the check - * that this message has already been sent. - */ - while (mDeferredMessages != null) { - Message cur = mDeferredMessages; - mDeferredMessages = mDeferredMessages.next; - cur.when = 0; - if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what=" - + cur.what + " target=" + cur.target); - sendMessageAtFrontOfQueue(cur); - } - if (mDbg) Log.d(TAG, "SmHandler.handleMessage X"); - } - } - - public HandlerState mCurrentState; - public HandlerState mDestState; - public Message mDeferredMessages; - } - - /** - * Create an active StateMachine, one that has a - * dedicated thread/looper/queue. - */ - public HandlerStateMachine(String name) { - mName = name; - mHandlerThread = new HandlerThread(name); - mHandlerThread.start(); - mHandler = new SmHandler(mHandlerThread.getLooper()); - } - - /** - * Get a message and set Message.target = this. - */ - public final Message obtainMessage() - { - Message msg = Message.obtain(mHandler); - if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target); - return msg; - } - - /** - * Get a message and set Message.target = this and - * Message.what = what. - */ - public final Message obtainMessage(int what) { - Message msg = Message.obtain(mHandler, what); - if (mDbg) { - Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what + - " target=" + msg.target); - } - return msg; - } - - /** - * Enqueue a message to this state machine. - */ - public final void sendMessage(Message msg) { - if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what); - mHandler.sendMessage(msg); - } - - /** - * Enqueue a message to this state machine after a delay. - */ - public final void sendMessageDelayed(Message msg, long delayMillis) { - if (mDbg) { - Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what=" - + msg.what + " delay=" + delayMillis); - } - mHandler.sendMessageDelayed(msg, delayMillis); - } - - /** - * Set the initial state. This must be invoked before - * and messages are sent to the state machine. - */ - public void setInitialState(HandlerState initialState) { - if (mDbg) { - Log.d(TAG, "StateMachine.setInitialState EX initialState" - + initialState.getClass().getName()); - } - mHandler.mDestState = initialState; - } - - /** - * transition to destination state. Upon returning - * from processMessage the current state's exit will - * be executed and upon the next message arriving - * destState.enter will be invoked. - */ - final public void transitionTo(HandlerState destState) { - if (mDbg) { - Log.d(TAG, "StateMachine.transitionTo EX destState" - + destState.getClass().getName()); - } - mHandler.mDestState = destState; - } - - /** - * Defer this message until next state transition. - * Upon transitioning all deferred messages will be - * placed on the queue and reprocessed in the original - * order. (i.e. The next state the oldest messages will - * be processed first) - */ - final public void deferMessage(Message msg) { - if (mDbg) { - Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages=" - + mHandler.mDeferredMessages); - } - - /* Copy the "msg" to "newMsg" as "msg" will be recycled */ - Message newMsg = obtainMessage(); - newMsg.copyFrom(msg); - - /* Place on front of queue */ - newMsg.next = mHandler.mDeferredMessages; - mHandler.mDeferredMessages = newMsg; - } - - /** - * @return the name - */ - public String getName() { - return mName; - } - - /** - * @return Handler - */ - public Handler getHandler() { - return mHandler; - } - - /** - * @return if debugging is enabled - */ - public boolean isDbg() { - return mDbg; - } - - /** - * Set debug enable/disabled. - */ - public void setDbg(boolean dbg) { - mDbg = dbg; - if (mDbg) { - mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG)); - } else { - mHandlerThread.getLooper().setMessageLogging(null); - } - } -} diff --git a/core/java/android/os/ICheckinService.aidl b/core/java/android/os/ICheckinService.aidl index e56b55d..619079a 100644 --- a/core/java/android/os/ICheckinService.aidl +++ b/core/java/android/os/ICheckinService.aidl @@ -26,21 +26,6 @@ import android.os.IParentalControlCallback; * {@hide} */ interface ICheckinService { - /** Synchronously attempt a checkin with the server, return true - * on success. - * @throws IllegalStateException whenever an error occurs. The - * cause of the exception will be the real exception: - * IOException for network errors, JSONException for invalid - * server responses, etc. - */ - boolean checkin(); - - /** Direct submission of crash data; returns after writing the crash. */ - void reportCrashSync(in byte[] crashData); - - /** Asynchronous "fire and forget" version of crash reporting. */ - oneway void reportCrashAsync(in byte[] crashData); - /** Reboot into the recovery system and wipe all user data. */ void masterClear(); diff --git a/core/java/android/provider/Checkin.java b/core/java/android/provider/Checkin.java index 84753ee..75936a1 100644 --- a/core/java/android/provider/Checkin.java +++ b/core/java/android/provider/Checkin.java @@ -23,7 +23,6 @@ import android.content.ContentValues; import android.database.SQLException; import android.net.Uri; import android.os.SystemClock; -import android.server.data.CrashData; import android.util.Log; import java.io.ByteArrayOutputStream; @@ -267,59 +266,4 @@ public final class Checkin { /** {@link SystemClock#elapsedRealtime} of the last time a crash report failed. */ static private volatile long sLastCrashFailureRealtime = -MIN_CRASH_FAILURE_RETRY; - - /** - * Helper function to report a crash. - * - * @param resolver from {@link android.content.Context#getContentResolver} - * @param crash data from {@link android.server.data.CrashData} - * @return URI of the crash report that was added - */ - static public Uri reportCrash(ContentResolver resolver, byte[] crash) { - try { - // If we are in a situation where crash reports fail (such as a full disk), - // it's important that we don't get into a loop trying to report failures. - // So discard all crash reports for a few seconds after reporting fails. - long realtime = SystemClock.elapsedRealtime(); - if (realtime - sLastCrashFailureRealtime < MIN_CRASH_FAILURE_RETRY) { - Log.e(TAG, "Crash logging skipped, too soon after logging failure"); - return null; - } - - // HACK: we don't support BLOB values, so base64 encode it. - byte[] encoded = Base64.encodeBase64(crash); - ContentValues values = new ContentValues(); - values.put(Crashes.DATA, new String(encoded)); - Uri uri = resolver.insert(Crashes.CONTENT_URI, values); - if (uri == null) { - Log.e(TAG, "Error reporting crash"); - sLastCrashFailureRealtime = SystemClock.elapsedRealtime(); - } - return uri; - } catch (Throwable t) { - // To avoid an infinite crash-reporting loop, swallow all errors and exceptions. - Log.e(TAG, "Error reporting crash: " + t); - sLastCrashFailureRealtime = SystemClock.elapsedRealtime(); - return null; - } - } - - /** - * Report a crash in CrashData format. - * - * @param resolver from {@link android.content.Context#getContentResolver} - * @param crash data to report - * @return URI of the crash report that was added - */ - static public Uri reportCrash(ContentResolver resolver, CrashData crash) { - try { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - crash.write(new DataOutputStream(data)); - return reportCrash(resolver, data.toByteArray()); - } catch (Throwable t) { - // Swallow all errors and exceptions when writing crash report - Log.e(TAG, "Error writing crash: " + t); - return null; - } - } } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 80d5d08..117de15 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1331,7 +1331,7 @@ public final class ContactsContract { * <p> * A sub-directory of a single raw contact that contains all of their * {@link ContactsContract.Data} rows. To access this directory append - * {@link Entity#CONTENT_DIRECTORY} to the contact URI. See + * {@link #CONTENT_DIRECTORY} to the contact URI. See * {@link RawContactsEntity} for a stand-alone table containing the same * data. * </p> diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ec0fe38..8c83303 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -3586,59 +3586,6 @@ public final class Settings { public static final String USE_LOCATION_FOR_SERVICES = "use_location"; /** - * The number of entries to fetch from the feed at a time for calendar sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_ENTRY_FETCH_QUEUE_SIZE = - "google_calendar_sync_entry_fetch_queue_size"; - - /** - * The number of entities to fetch from the provider at a time for calendar sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_ENTITY_FETCH_QUEUE_SIZE = - "google_calendar_sync_entity_fetch_queue_size"; - - /** - * The maximum number of simultaneous changes to allow before alerting the user for - * calendar sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_NUM_ALLOWED_SIMULTANEOUS_CHANGES = - "google_calendar_sync_num_allowed_simultaneous changes"; - - /** - * The maximum percentage of simultaneous changes to allow before alerting the user for - * calendar sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_PERCENT_ALLOWED_SIMULTANEOUS_CHANGES = - "google_calendar_sync_percent_allowed_simultaneous_changes"; - - /** - * The number of times to apply local changes in calendar sync before stopping. - */ - public static final String GOOGLE_CALENDAR_SYNC_MAX_LOOP_ATTEMPTS = - "google_calendar_sync_max_loop_attempts"; - - /** - * The batch size for applying server changes to the provider for calendar sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_NUM_APPLICATIONS_PER_BATCH = - "google_calendar_sync_num_applications_per_batch"; - - /** - * The length of the calendar sync window into the future. - * This specifies the number of days into the future for the sliding window sync. - * Setting this to zero will disable sliding sync. - */ - public static final String GOOGLE_CALENDAR_SYNC_WINDOW_DAYS = - "google_calendar_sync_window_days"; - - /** - * How often to update the calendar sync window. - * The window will be advanced every n days. - */ - public static final String GOOGLE_CALENDAR_SYNC_WINDOW_UPDATE_DAYS = - "google_calendar_sync_window_update_days"; - - /** * The number of promoted sources in GlobalSearch. */ public static final String SEARCH_NUM_PROMOTED_SOURCES = "search_num_promoted_sources"; diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 5dd016f..b590449 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -37,6 +37,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences; import android.os.Binder; import android.os.Handler; import android.os.IBinder; @@ -72,7 +73,6 @@ public class BluetoothService extends IBluetooth.Stub { private int mNativeData; private BluetoothEventLoop mEventLoop; - private IntentFilter mIntentFilter; private boolean mIsAirplaneSensitive; private int mBluetoothState; private boolean mRestart = false; // need to call enable() after disable() @@ -89,6 +89,9 @@ public class BluetoothService extends IBluetooth.Stub { private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr"; private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin"; + private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address"; + private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings"; + private static final int MESSAGE_REGISTER_SDP_RECORDS = 1; private static final int MESSAGE_FINISH_DISABLE = 2; private static final int MESSAGE_UUID_INTENT = 3; @@ -163,32 +166,14 @@ public class BluetoothService extends IBluetooth.Stub { mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); mServiceRecordToPid = new HashMap<Integer, Integer>(); - registerForAirplaneMode(); IntentFilter filter = new IntentFilter(); + registerForAirplaneMode(filter); + filter.addAction(Intent.ACTION_DOCK_EVENT); - mContext.registerReceiver(mBroadcastReceiver, filter); + mContext.registerReceiver(mReceiver, filter); } - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent != null) { - String action = intent.getAction(); - - if (Intent.ACTION_DOCK_EVENT.equals(action)) { - int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, - Intent.EXTRA_DOCK_STATE_UNDOCKED); - if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state); - if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { - mDockAddress = null; - mDockPin = null; - } - } - } - } - }; - public static synchronized String readDockBluetoothAddress() { if (mDockAddress != null) return mDockAddress; @@ -263,9 +248,7 @@ public class BluetoothService extends IBluetooth.Stub { @Override protected void finalize() throws Throwable { - if (mIsAirplaneSensitive) { - mContext.unregisterReceiver(mReceiver); - } + mContext.unregisterReceiver(mReceiver); try { cleanupNativeDataNative(); } finally { @@ -1086,8 +1069,10 @@ public class BluetoothService extends IBluetooth.Stub { } public synchronized boolean isBluetoothDock(String address) { - if (address.equals(mDockAddress)) return true; - return false; + SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, + mContext.MODE_PRIVATE); + + return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address); } /*package*/ boolean isRemoteDeviceInCache(String address) { @@ -1577,6 +1562,8 @@ public class BluetoothService extends IBluetooth.Stub { private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { + if (intent == null) return; + String action = intent.getAction(); if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { ContentResolver resolver = context.getContentResolver(); @@ -1591,18 +1578,31 @@ public class BluetoothService extends IBluetooth.Stub { disable(false); } } + } else if (Intent.ACTION_DOCK_EVENT.equals(action)) { + int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, + Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state); + if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) { + mDockAddress = null; + mDockPin = null; + } else { + SharedPreferences.Editor editor = + mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, + mContext.MODE_PRIVATE).edit(); + editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true); + editor.commit(); + } } } }; - private void registerForAirplaneMode() { + private void registerForAirplaneMode(IntentFilter filter) { String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_RADIOS); mIsAirplaneSensitive = airplaneModeRadios == null ? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH); if (mIsAirplaneSensitive) { - mIntentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED); - mContext.registerReceiver(mReceiver, mIntentFilter); + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); } } diff --git a/core/java/android/server/data/BuildData.java b/core/java/android/server/data/BuildData.java deleted file mode 100644 index 53ffa3f..0000000 --- a/core/java/android/server/data/BuildData.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2007 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server.data; - -import android.os.Build; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import static com.android.internal.util.Objects.nonNull; - -/** - * Build data transfer object. Keep in sync. with the server side version. - */ -public class BuildData { - - /** The version of the data returned by write() and understood by the constructor. */ - private static final int VERSION = 0; - - private final String fingerprint; - private final String incrementalVersion; - private final long time; // in *seconds* since the epoch (not msec!) - - public BuildData() { - this.fingerprint = "android:" + Build.FINGERPRINT; - this.incrementalVersion = Build.VERSION.INCREMENTAL; - this.time = Build.TIME / 1000; // msec -> sec - } - - public BuildData(String fingerprint, String incrementalVersion, long time) { - this.fingerprint = nonNull(fingerprint); - this.incrementalVersion = incrementalVersion; - this.time = time; - } - - /*package*/ BuildData(DataInput in) throws IOException { - int dataVersion = in.readInt(); - if (dataVersion != VERSION) { - throw new IOException("Expected " + VERSION + ". Got: " + dataVersion); - } - - this.fingerprint = in.readUTF(); - this.incrementalVersion = Long.toString(in.readLong()); - this.time = in.readLong(); - } - - /*package*/ void write(DataOutput out) throws IOException { - out.writeInt(VERSION); - out.writeUTF(fingerprint); - - // TODO: change the format/version to expect a string for this field. - // Version 0, still used by the server side, expects a long. - long changelist; - try { - changelist = Long.parseLong(incrementalVersion); - } catch (NumberFormatException ex) { - changelist = -1; - } - out.writeLong(changelist); - out.writeLong(time); - } - - public String getFingerprint() { - return fingerprint; - } - - public String getIncrementalVersion() { - return incrementalVersion; - } - - public long getTime() { - return time; - } -} diff --git a/core/java/android/server/data/CrashData.java b/core/java/android/server/data/CrashData.java deleted file mode 100644 index d652bb3..0000000 --- a/core/java/android/server/data/CrashData.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server.data; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -import static com.android.internal.util.Objects.nonNull; - -/** - * Crash data transfer object. Keep in sync. with the server side version. - */ -public class CrashData { - - final String id; - final String activity; - final long time; - final BuildData buildData; - final ThrowableData throwableData; - final byte[] state; - - public CrashData(String id, String activity, BuildData buildData, - ThrowableData throwableData) { - this.id = nonNull(id); - this.activity = nonNull(activity); - this.buildData = nonNull(buildData); - this.throwableData = nonNull(throwableData); - this.time = System.currentTimeMillis(); - this.state = null; - } - - public CrashData(String id, String activity, BuildData buildData, - ThrowableData throwableData, byte[] state) { - this.id = nonNull(id); - this.activity = nonNull(activity); - this.buildData = nonNull(buildData); - this.throwableData = nonNull(throwableData); - this.time = System.currentTimeMillis(); - this.state = state; - } - - public CrashData(DataInput in) throws IOException { - int dataVersion = in.readInt(); - if (dataVersion != 0 && dataVersion != 1) { - throw new IOException("Expected 0 or 1. Got: " + dataVersion); - } - - this.id = in.readUTF(); - this.activity = in.readUTF(); - this.time = in.readLong(); - this.buildData = new BuildData(in); - this.throwableData = new ThrowableData(in); - if (dataVersion == 1) { - int len = in.readInt(); - if (len == 0) { - this.state = null; - } else { - this.state = new byte[len]; - in.readFully(this.state, 0, len); - } - } else { - this.state = null; - } - } - - public CrashData(String tag, Throwable throwable) { - id = ""; - activity = tag; - buildData = new BuildData(); - throwableData = new ThrowableData(throwable); - time = System.currentTimeMillis(); - state = null; - } - - public void write(DataOutput out) throws IOException { - // version - if (this.state == null) { - out.writeInt(0); - } else { - out.writeInt(1); - } - - out.writeUTF(this.id); - out.writeUTF(this.activity); - out.writeLong(this.time); - buildData.write(out); - throwableData.write(out); - if (this.state != null) { - out.writeInt(this.state.length); - out.write(this.state, 0, this.state.length); - } - } - - public BuildData getBuildData() { - return buildData; - } - - public ThrowableData getThrowableData() { - return throwableData; - } - - public String getId() { - return id; - } - - public String getActivity() { - return activity; - } - - public long getTime() { - return time; - } - - public byte[] getState() { - return state; - } - - /** - * Return a brief description of this CrashData record. The details of the - * representation are subject to change. - * - * @return Returns a String representing the contents of the object. - */ - @Override - public String toString() { - return "[CrashData: id=" + id + " activity=" + activity + " time=" + time + - " buildData=" + buildData.toString() + - " throwableData=" + throwableData.toString() + "]"; - } -} diff --git a/core/java/android/server/data/StackTraceElementData.java b/core/java/android/server/data/StackTraceElementData.java deleted file mode 100644 index 07185a0..0000000 --- a/core/java/android/server/data/StackTraceElementData.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server.data; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -/** - * Stack trace element data transfer object. Keep in sync. with the server side - * version. - */ -public class StackTraceElementData { - - final String className; - final String fileName; - final String methodName; - final int lineNumber; - - public StackTraceElementData(StackTraceElement element) { - this.className = element.getClassName(); - - String fileName = element.getFileName(); - this.fileName = fileName == null ? "[unknown source]" : fileName; - - this.methodName = element.getMethodName(); - this.lineNumber = element.getLineNumber(); - } - - public StackTraceElementData(DataInput in) throws IOException { - int dataVersion = in.readInt(); - if (dataVersion != 0) { - throw new IOException("Expected 0. Got: " + dataVersion); - } - - this.className = in.readUTF(); - this.fileName = in.readUTF(); - this.methodName = in.readUTF(); - this.lineNumber = in.readInt(); - } - - void write(DataOutput out) throws IOException { - out.writeInt(0); // version - - out.writeUTF(className); - out.writeUTF(fileName); - out.writeUTF(methodName); - out.writeInt(lineNumber); - } - - public String getClassName() { - return className; - } - - public String getFileName() { - return fileName; - } - - public String getMethodName() { - return methodName; - } - - public int getLineNumber() { - return lineNumber; - } -} diff --git a/core/java/android/server/data/ThrowableData.java b/core/java/android/server/data/ThrowableData.java deleted file mode 100644 index e500aca..0000000 --- a/core/java/android/server/data/ThrowableData.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.server.data; - -import java.io.DataInput; -import java.io.DataOutput; -import java.io.IOException; - -/** - * Throwable data transfer object. Keep in sync. with the server side version. - */ -public class ThrowableData { - - final String message; - final String type; - final StackTraceElementData[] stackTrace; - final ThrowableData cause; - - public ThrowableData(Throwable throwable) { - this.type = throwable.getClass().getName(); - String message = throwable.getMessage(); - this.message = message == null ? "" : message; - - StackTraceElement[] elements = throwable.getStackTrace(); - this.stackTrace = new StackTraceElementData[elements.length]; - for (int i = 0; i < elements.length; i++) { - this.stackTrace[i] = new StackTraceElementData(elements[i]); - } - - Throwable cause = throwable.getCause(); - this.cause = cause == null ? null : new ThrowableData(cause); - } - - public ThrowableData(DataInput in) throws IOException { - int dataVersion = in.readInt(); - if (dataVersion != 0) { - throw new IOException("Expected 0. Got: " + dataVersion); - } - - this.message = in.readUTF(); - this.type = in.readUTF(); - - int count = in.readInt(); - this.stackTrace = new StackTraceElementData[count]; - for (int i = 0; i < count; i++) { - this.stackTrace[i] = new StackTraceElementData(in); - } - - this.cause = in.readBoolean() ? new ThrowableData(in) : null; - } - - public void write(DataOutput out) throws IOException { - out.writeInt(0); // version - - out.writeUTF(message); - out.writeUTF(type); - - out.writeInt(stackTrace.length); - for (StackTraceElementData elementData : stackTrace) { - elementData.write(out); - } - - out.writeBoolean(cause != null); - if (cause != null) { - cause.write(out); - } - } - - public String getMessage() { - return message; - } - - public String getType() { - return type; - } - - public StackTraceElementData[] getStackTrace() { - return stackTrace; - } - - public ThrowableData getCause() { - return cause; - } - - - public String toString() { - return toString(null); - } - - public String toString(String prefix) { - StringBuilder builder = new StringBuilder(); - append(prefix, builder, this); - return builder.toString(); - } - - private static void append(String prefix, StringBuilder builder, - ThrowableData throwableData) { - if (prefix != null) builder.append(prefix); - builder.append(throwableData.getType()) - .append(": ") - .append(throwableData.getMessage()) - .append('\n'); - for (StackTraceElementData element : throwableData.getStackTrace()) { - if (prefix != null ) builder.append(prefix); - builder.append(" at ") - .append(element.getClassName()) - .append('.') - .append(element.getMethodName()) - .append("(") - .append(element.getFileName()) - .append(':') - .append(element.getLineNumber()) - .append(")\n"); - - } - - ThrowableData cause = throwableData.getCause(); - if (cause != null) { - if (prefix != null ) builder.append(prefix); - builder.append("Caused by: "); - append(prefix, builder, cause); - } - } -} diff --git a/core/java/android/server/data/package.html b/core/java/android/server/data/package.html deleted file mode 100755 index 1c9bf9d..0000000 --- a/core/java/android/server/data/package.html +++ /dev/null @@ -1,5 +0,0 @@ -<html> -<body> - {@hide} -</body> -</html> diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 789172f..e27804c 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -254,6 +254,12 @@ implements MovementMethod buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); + + // Disallow intercepting of the touch events, so that + // users can scroll and select at the same time. + // without this, users would get booted out of select + // mode once the view detected it needed to scroll. + widget.getParent().requestDisallowInterceptTouchEvent(true); } } else if (event.getAction() == MotionEvent.ACTION_MOVE ) { boolean cap = (MetaKeyKeyListener.getMetaState(buffer, @@ -277,7 +283,7 @@ implements MovementMethod int spanstart; int spanend; if (offset >= lastDownOffset) { - // expand to from word start of the original tap to new word + // Expand from word start of the original tap to new word // end, since we are selecting "forwards" spanstart = findWordStart(buffer, lastDownOffset); spanend = findWordEnd(buffer, offset); diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index aa8d979..42ad10e 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -24,6 +24,7 @@ import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; +import android.view.KeyEvent; public class Touch { private Touch() { } @@ -139,10 +140,21 @@ public class Touch { if (ds[0].mFarEnough) { ds[0].mUsed = true; - - float dx = ds[0].mX - event.getX(); - float dy = ds[0].mY - event.getY(); - + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); + float dx; + float dy; + if (cap) { + // if we're selecting, we want the scroll to go in + // the direction of the drag + dx = event.getX() - ds[0].mX; + dy = event.getY() - ds[0].mY; + } else { + dx = ds[0].mX - event.getX(); + dy = ds[0].mY - event.getY(); + } ds[0].mX = event.getX(); ds[0].mY = event.getY(); diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index 2572679..e95d0be 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -216,9 +216,7 @@ public final class Log { * @param tr An exception to log */ public static int e(String tag, String msg, Throwable tr) { - int r = println(ERROR, tag, msg + '\n' + getStackTraceString(tr)); - RuntimeInit.reportException(tag, tr, false); // asynchronous - return r; + return println(ERROR, tag, msg + '\n' + getStackTraceString(tr)); } /** diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d3f46b8..7f5d254 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3771,6 +3771,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Dispatch a view visibility change down the view hierarchy. + * ViewGroups should override to route to their children. + * @param changedView The view whose visibility changed. Could be 'this' or + * an ancestor view. + * @param visibility The new visibility of changedView. + */ + protected void dispatchVisibilityChanged(View changedView, int visibility) { + onVisibilityChanged(changedView, visibility); + } + + /** + * Called when the visibility of the view or an ancestor of the view is changed. + * @param changedView The view whose visibility changed. Could be 'this' or + * an ancestor view. + * @param visibility The new visibility of changedView. + */ + protected void onVisibilityChanged(View changedView, int visibility) { + } + + /** * Dispatch a window visibility change down the view hierarchy. * ViewGroups should override to route to their children. * @@ -4349,6 +4369,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } } + if ((changed & VISIBILITY_MASK) != 0) { + dispatchVisibilityChanged(this, (flags & VISIBILITY_MASK)); + } + if ((changed & WILL_NOT_CACHE_DRAWING) != 0) { destroyDrawingCache(); } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index e2f15c7..6646136 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -684,6 +684,19 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * {@inheritDoc} */ @Override + protected void dispatchVisibilityChanged(View changedView, int visibility) { + super.dispatchVisibilityChanged(changedView, visibility); + final int count = mChildrenCount; + final View[] children = mChildren; + for (int i = 0; i < count; i++) { + children[i].dispatchVisibilityChanged(changedView, visibility); + } + } + + /** + * {@inheritDoc} + */ + @Override public void dispatchWindowVisibilityChanged(int visibility) { super.dispatchWindowVisibilityChanged(visibility); final int count = mChildrenCount; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 93e95bf..a974653 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -700,6 +700,9 @@ public class WebView extends AbsoluteLayout public void onVisibilityChanged(boolean visible) { if (visible) { switchOutDrawHistory(); + // Bring back the hidden zoom controls. + mZoomButtonsController.getZoomControls().setVisibility( + View.VISIBLE); updateZoomButtonsEnabled(); } } @@ -788,9 +791,6 @@ public class WebView extends AbsoluteLayout // button, if the page cannot zoom mZoomButtonsController.getZoomControls().setVisibility(View.GONE); } else { - // Bring back the hidden zoom controls. - mZoomButtonsController.getZoomControls() - .setVisibility(View.VISIBLE); // Set each one individually, as a page may be able to zoom in // or out. mZoomButtonsController.setZoomInEnabled(canZoomIn); @@ -3798,9 +3798,17 @@ public class WebView extends AbsoluteLayout } } - // we always force, in case our height changed, in which case we still - // want to send the notification over to webkit - setNewZoomScale(mActualScale, true); + // onSizeChanged() is called during WebView layout. And any + // requestLayout() is blocked during layout. As setNewZoomScale() will + // call its child View to reposition itself through ViewManager's + // scaleAll(), we need to post a Runnable to ensure requestLayout(). + post(new Runnable() { + public void run() { + // we always force, in case our height changed, in which case we + // still want to send the notification over to webkit + setNewZoomScale(mActualScale, true); + } + }); } @Override @@ -5228,6 +5236,10 @@ public class WebView extends AbsoluteLayout // particular when the user was on a password field, so // the WebTextView was visible. clearTextEntry(); + // update the zoom buttons as the scale can be changed + if (getSettings().getBuiltInZoomControls()) { + updateZoomButtonsEnabled(); + } } // We update the layout (i.e. request a layout from the // view system) if the last view size that we sent to diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index c782c8c..b7bb72d 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -17,7 +17,9 @@ package com.android.internal.os; import android.app.ActivityManagerNative; +import android.app.ApplicationErrorReport; import android.app.IActivityManager; +import android.os.Build; import android.os.Debug; import android.os.IBinder; import android.os.ICheckinService; @@ -25,8 +27,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.os.Build; -import android.server.data.CrashData; import android.util.Config; import android.util.Log; @@ -309,51 +309,18 @@ public class RuntimeInit { */ public static void crash(String tag, Throwable t) { if (mApplicationObject != null) { - byte[] crashData = null; try { // Log exception. Log.e(TAG, Log.getStackTraceString(t)); - crashData = marshallException(tag, t); - if (crashData == null) { - throw new NullPointerException("Can't marshall crash data"); - } - } catch (Throwable t2) { - try { - // Log exception as a string so we don't get in an infinite loop. - Log.e(TAG, "Error reporting crash: " - + Log.getStackTraceString(t2)); - } catch (Throwable t3) { - // Do nothing, must be OOM so we can't format the message - } - } - - try { - // Display user-visible error message. - String msg = t.getMessage(); - if (msg == null) { - msg = t.toString(); - } + // Show a message to the user. IActivityManager am = ActivityManagerNative.getDefault(); - try { - int res = am.handleApplicationError(mApplicationObject, - 0, tag, msg, t.toString(), crashData); - // Is waiting for the debugger the right thing? - // For now I have turned off the Debug button, because - // I'm not sure what we should do if it is actually - // selected. - //Log.i(TAG, "Got app error result: " + res); - if (res == 1) { - Debug.waitForDebugger(); - return; - } - } catch (RemoteException e) { - } + am.handleApplicationError(mApplicationObject, tag, + new ApplicationErrorReport.CrashInfo(t)); } catch (Throwable t2) { try { // Log exception as a string so we don't get in an infinite loop. - Log.e(TAG, "Error reporting crash: " - + Log.getStackTraceString(t2)); + Log.e(TAG, "Error reporting crash: " + Log.getStackTraceString(t2)); } catch (Throwable t3) { // Do nothing, must be OOM so we can't format the message } @@ -366,7 +333,6 @@ public class RuntimeInit { try { Log.e(TAG, "*** EXCEPTION IN SYSTEM PROCESS. System will crash."); Log.e(tag, Log.getStackTraceString(t)); - reportException(tag, t, true); // synchronous } catch (Throwable t2) { // Do nothing, must be OOM so we can't format the message } finally { @@ -381,82 +347,6 @@ public class RuntimeInit { private static final AtomicInteger sInReportException = new AtomicInteger(); /** - * Report an error in the current process. The exception information will - * be handed off to the checkin service and eventually uploaded for analysis. - * This is expensive! Only use this when the exception indicates a programming - * error ("should not happen"). - * - * @param tag to use when logging the error - * @param t exception that was generated by the error - * @param sync true to wait for the report, false to "fire and forget" - */ - public static void reportException(String tag, Throwable t, boolean sync) { - if (!initialized) { - // Exceptions during, eg, zygote cannot use this mechanism - return; - } - - // It's important to prevent an infinite crash-reporting loop: - // while this function is running, don't let it be called again. - int reenter = sInReportException.getAndIncrement(); - if (reenter != 0) { - sInReportException.decrementAndGet(); - Log.e(TAG, "Crash logging skipped, already logging another crash"); - return; - } - - // TODO: Enable callers to specify a level (i.e. warn or error). - try { - // Submit crash data to statistics service. - byte[] crashData = marshallException(tag, t); - ICheckinService checkin = ICheckinService.Stub.asInterface( - ServiceManager.getService("checkin")); - if (checkin == null) { - Log.e(TAG, "Crash logging skipped, no checkin service"); - } else if (sync) { - checkin.reportCrashSync(crashData); - } else { - checkin.reportCrashAsync(crashData); - } - } catch (Throwable t2) { - // Log exception as a string so we don't get in an infinite loop. - Log.e(TAG, "Crash logging failed: " + t2); - } finally { - sInReportException.decrementAndGet(); - } - } - - private static byte[] marshallException(String tag, Throwable t) { - // Convert crash data to bytes. - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - DataOutputStream dout = new DataOutputStream(bout); - try { - new CrashData(tag, t).write(dout); - dout.close(); - } catch (IOException e) { - return null; - } - return bout.toByteArray(); - } - - /** - * Replay an encoded CrashData record back into a useable CrashData record. This can be - * helpful for providing debugging output after a process error. - * - * @param crashDataBytes The byte array containing the encoded crash record - * @return new CrashData record, or null if could not create one. - */ - public static CrashData unmarshallException(byte[] crashDataBytes) { - try { - ByteArrayInputStream bin = new ByteArrayInputStream(crashDataBytes); - DataInputStream din = new DataInputStream(bin); - return new CrashData(din); - } catch (IOException e) { - return null; - } - } - - /** * Set the object identifying this application/process, for reporting VM * errors. */ @@ -473,5 +363,4 @@ public class RuntimeInit { } private static IBinder mApplicationObject; - } diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/HierarchicalState.java new file mode 100644 index 0000000..002338a --- /dev/null +++ b/core/java/com/android/internal/util/HierarchicalState.java @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2009 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.internal.util; + +import android.os.Message; + +/** + * {@hide} + * + * The abstract class for implementing states in a + * HierarchicalStateMachine and HandlerStateMachine. + */ +public abstract class HierarchicalState { + + /** + * Constructor + */ + protected HierarchicalState() { + } + + /** + * Called when a state is entered. + */ + protected void enter() { + } + + /** + * Called when a message is to be processed by the + * state machine. + * + * This routine is never reentered thus no synchronization + * is needed as only one processMessage method will ever be + * executing within a state machine at any given time. This + * does mean that processing by this routine must be completed + * as expeditiously as possible as no subsequent messages will + * be processed until this routine returns. + * + * @param msg to process + * @return true if processing has completed and false + * if the parent state's processMessage should + * be invoked. + */ + abstract protected boolean processMessage(Message msg); + + /** + * Called when a state is exited. + */ + protected void exit() { + } + + /** + * @return name of state, but default returns the states + * class name. An instance name would be better but requiring + * it seems unnecessary. + */ + public String getName() { + String name = getClass().getName(); + int lastDollar = name.lastIndexOf('$'); + return name.substring(lastDollar + 1); + } +} diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java new file mode 100644 index 0000000..a1c5078 --- /dev/null +++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java @@ -0,0 +1,1164 @@ +/** + * Copyright (C) 2009 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.internal.util; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * {@hide} + * + * A hierarchical state machine is a state machine which processes messages + * and can have states arranged hierarchically. A state is a <code>HierarchicalState</code> + * object and must implement <code>processMessage</code> and optionally <code>enter/exit/getName</code>. + * The enter/exit methods are equivalent to the construction and destruction + * in Object Oriented programming and are used to perform initialization and + * cleanup of the state respectively. The <code>getName</code> method returns the + * name of the state the default implementation returns the class name it may be + * desirable to have this return the name of the state instance name instead. + * In particular if a particular state class has multiple instances. + * + * When a state machine is created <code>addState</code> is used to build the + * hierarchy and <code>setInitialState</code> is used to identify which of these + * is the initial state. After construction the programmer calls <code>start</code> + * which initializes the state machine and calls <code>enter</code> for all of the initial + * state's hierarchy, starting at its eldest parent. For example given the simple + * state machine below after start is called mP1.enter will have been called and + * then mS1.enter. +<code> + mP1 + / \ + mS2 mS1 ----> initial state +</code> + * After the state machine is created and started, messages are sent to a state + * machine using <code>sendMessage</code and the messages are created using + * <code>obtainMessage</code>. When the state machine receives a message the + * current state's <code>processMessage</code> is invoked. In the above example + * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> + * to change the current state to a new state + * + * Each state in the state machine may have a zero or one parent states and if + * a child state is unable to handle a message it may have the message processed + * by its parent by returning false. If a message is never processed <code>unhandledMessage</code> + * will be invoked to give one last chance for the state machine to process + * the message. + * + * When all processing is completed a state machine may choose to call + * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> + * returns the state machine will transfer to an internal <code>HaltingState</code> + * and invoke <code>halting</code>. Any message subsequently received by the state + * machine will cause <code>haltedProcessMessage</code> to be invoked. + * + * In addition to <code>processMessage</code> each <code>HierarchicalState</code> has + * an <code>enter</code> method and <code>exit</exit> method which may be overridden. + * + * Since the states are arranged in a hierarchy transitioning to a new state + * causes current states to be exited and new states to be entered. To determine + * the list of states to be entered/exited the common parent closest to + * the current state is found. We then exit from the current state and its + * parent's up to but not including the common parent state and then enter all + * of the new states below the common parent down to the destination state. + * If there is no common parent all states are exited and then the new states + * are entered. + * + * Two other methods that states can use are <code>deferMessage</code> and + * <code>sendMessageAtFrontOfQueue</code>. The <code>sendMessageAtFrontOfQueue</code> sends + * a message but places it on the front of the queue rather than the back. The + * <code>deferMessage</code> causes the message to be saved on a list until a + * transition is made to a new state. At which time all of the deferred messages + * will be put on the front of the state machine queue with the oldest message + * at the front. These will then be processed by the new current state before + * any other messages that are on the queue or might be added later. Both of + * these are protected and may only be invoked from within a state machine. + * + * To illustrate some of these properties we'll use state machine with 8 + * state hierarchy: +<code> + mP0 + / \ + mP1 mS0 + / \ + mS2 mS1 + / \ \ + mS3 mS4 mS5 ---> initial state +</code> + * + * After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. + * So the order of calling processMessage when a message is received is mS5, + * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this + * message by returning false. + * + * Now assume mS5.processMessage receives a message it can handle, and during + * the handling determines the machine should changes states. It would call + * transitionTo(mS4) and return true. Immediately after returning from + * processMessage the state machine runtime will find the common parent, + * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then + * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So + * when the next message is received mS4.processMessage will be invoked. + * + * To assist in describing an HSM a simple grammar has been created which + * is informally defined here and a formal EBNF description is at the end + * of the class comment. + * + * An HSM starts with the name and includes a set of hierarchical states. + * A state is preceeded by one or more plus signs (+), to indicate its + * depth and a hash (#) if its the initial state. Child states follow their + * parents and have one more plus sign then their parent. Inside a state + * are a series of messages, the actions they perform and if the processing + * is complete ends with a period (.). If processing isn't complete and + * the parent should process the message it ends with a caret (^). The + * actions include send a message ($MESSAGE), defer a message (%MESSAGE), + * transition to a new state (>MESSAGE) and an if statement + * (if ( expression ) { list of actions }.) + * + * The Hsm HelloWorld could documented as: + * + * HelloWorld { + * + # mState1. + * } + * + * and interpreted as HSM HelloWorld: + * + * mState1 a root state (single +) and initial state (#) which + * processes all messages completely, the period (.). + * + * The implementation is: +<code> +class HelloWorld extends HierarchicalStateMachine { + Hsm1(String name) { + super(name); + addState(mState1); + setInitialState(mState1); + } + + public static HelloWorld makeHelloWorld() { + HelloWorld hw = new HelloWorld("hw"); + hw.start(); + return hw; + } + + class State1 extends HierarchicalState { + @Override public boolean processMessage(Message message) { + Log.d(TAG, "Hello World"); + return true; + } + } + State1 mState1 = new State1(); +} + +void testHelloWorld() { + HelloWorld hw = makeHelloWorld(); + hw.sendMessage(hw.obtainMessage()); +} +</code> + * + * A more interesting state machine is one of four states + * with two independent parent states. +<code> + mP1 mP2 + / \ + mS2 mS1 +</code> + * + * documented as: + * + * Hsm1 { + * + mP1 { + * CMD_2 { + * $CMD_3 + * %CMD_2 + * >mS2 + * }. + * } + * ++ # mS1 { CMD_1{ >mS1 }^ } + * ++ mS2 { + * CMD_2{$CMD_4}. + * CMD_3{%CMD_3 ; >mP2}. + * } + * + * + mP2 e($CMD_5) { + * CMD_3, CMD_4. + * CMD_5{>HALT}. + * } + * } + * + * and interpreted as HierarchicalStateMachine Hsm1: + * + * mP1 a root state. + * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2 + * + * mS1 a child of mP1 is the initial state: + * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it. + * + * mS2 a child of mP1: + * processes message CMD_2 which send CMD_4 + * processes message CMD_3 which defers CMD_3 and transitions to mP2 + * + * mP2 a root state. + * on enter it sends CMD_5 + * processes message CMD_3 + * processes message CMD_4 + * processes message CMD_5 which transitions to halt state + * + * The implementation is below and also in HierarchicalStateMachineTest: +<code> +class Hsm1 extends HierarchicalStateMachine { + private static final String TAG = "hsm1"; + + public static final int CMD_1 = 1; + public static final int CMD_2 = 2; + public static final int CMD_3 = 3; + public static final int CMD_4 = 4; + public static final int CMD_5 = 5; + + public static Hsm1 makeHsm1() { + Log.d(TAG, "makeHsm1 E"); + Hsm1 sm = new Hsm1("hsm1"); + sm.start(); + Log.d(TAG, "makeHsm1 X"); + return sm; + } + + Hsm1(String name) { + super(name); + Log.d(TAG, "ctor E"); + + // Add states, use indentation to show hierarchy + addState(mP1); + addState(mS1, mP1); + addState(mS2, mP1); + addState(mP2); + + // Set the initial state + setInitialState(mS1); + Log.d(TAG, "ctor X"); + } + + class P1 extends HierarchicalState { + @Override public void enter() { + Log.d(TAG, "mP1.enter"); + } + @Override public boolean processMessage(Message message) { + boolean retVal; + Log.d(TAG, "mP1.processMessage what=" + message.what); + switch(message.what) { + case CMD_2: + // CMD_2 will arrive in mS2 before CMD_3 + sendMessage(obtainMessage(CMD_3)); + deferMessage(message); + transitionTo(mS2); + retVal = true; + break; + default: + // Any message we don't understand in this state invokes unhandledMessage + retVal = false; + break; + } + return retVal; + } + @Override public void exit() { + Log.d(TAG, "mP1.exit"); + } + } + + class S1 extends HierarchicalState { + @Override public void enter() { + Log.d(TAG, "mS1.enter"); + } + @Override public boolean processMessage(Message message) { + Log.d(TAG, "S1.processMessage what=" + message.what); + if (message.what == CMD_1) { + // Transition to ourself to show that enter/exit is called + transitionTo(mS1); + return true; + } else { + // Let parent process all other messages + return false; + } + } + @Override public void exit() { + Log.d(TAG, "mS1.exit"); + } + } + + class S2 extends HierarchicalState { + @Override public void enter() { + Log.d(TAG, "mS2.enter"); + } + @Override public boolean processMessage(Message message) { + boolean retVal; + Log.d(TAG, "mS2.processMessage what=" + message.what); + switch(message.what) { + case(CMD_2): + sendMessage(obtainMessage(CMD_4)); + retVal = true; + break; + case(CMD_3): + deferMessage(message); + transitionTo(mP2); + retVal = true; + break; + default: + retVal = false; + break; + } + return retVal; + } + @Override public void exit() { + Log.d(TAG, "mS2.exit"); + } + } + + class P2 extends HierarchicalState { + @Override public void enter() { + Log.d(TAG, "mP2.enter"); + sendMessage(obtainMessage(CMD_5)); + } + @Override public boolean processMessage(Message message) { + Log.d(TAG, "P2.processMessage what=" + message.what); + switch(message.what) { + case(CMD_3): + break; + case(CMD_4): + break; + case(CMD_5): + transitionToHaltingState(); + break; + } + return true; + } + @Override public void exit() { + Log.d(TAG, "mP2.exit"); + } + } + + @Override + protected void halting() { + Log.d(TAG, "halting"); + synchronized (this) { + this.notifyAll(); + } + } + + P1 mP1 = new P1(); + S1 mS1 = new S1(); + S2 mS2 = new S2(); + P2 mP2 = new P2(); +} +</code> + * + * If this is executed by sending two messages CMD_1 and CMD_2 + * (Note the synchronize is only needed because we use hsm.wait()) + * + * Hsm1 hsm = makeHsm1(); + * synchronize(hsm) { + * hsm.sendMessage(obtainMessage(hsm.CMD_1)); + * hsm.sendMessage(obtainMessage(hsm.CMD_2)); + * try { + * // wait for the messages to be handled + * hsm.wait(); + * } catch (InterruptedException e) { + * Log.e(TAG, "exception while waiting " + e.getMessage()); + * } + * } + * + * + * The output is: + * + * D/hsm1 ( 1999): makeHsm1 E + * D/hsm1 ( 1999): ctor E + * D/hsm1 ( 1999): ctor X + * D/hsm1 ( 1999): mP1.enter + * D/hsm1 ( 1999): mS1.enter + * D/hsm1 ( 1999): makeHsm1 X + * D/hsm1 ( 1999): mS1.processMessage what=1 + * D/hsm1 ( 1999): mS1.exit + * D/hsm1 ( 1999): mS1.enter + * D/hsm1 ( 1999): mS1.processMessage what=2 + * D/hsm1 ( 1999): mP1.processMessage what=2 + * D/hsm1 ( 1999): mS1.exit + * D/hsm1 ( 1999): mS2.enter + * D/hsm1 ( 1999): mS2.processMessage what=2 + * D/hsm1 ( 1999): mS2.processMessage what=3 + * D/hsm1 ( 1999): mS2.exit + * D/hsm1 ( 1999): mP1.exit + * D/hsm1 ( 1999): mP2.enter + * D/hsm1 ( 1999): mP2.processMessage what=3 + * D/hsm1 ( 1999): mP2.processMessage what=4 + * D/hsm1 ( 1999): mP2.processMessage what=5 + * D/hsm1 ( 1999): mP2.exit + * D/hsm1 ( 1999): halting + * + * Here is the HSM a BNF grammar, this is a first stab at creating an + * HSM description language, suggestions corrections or alternatives + * would be much appreciated. + * + * Legend: + * {} ::= zero or more + * {}+ ::= one or more + * [] ::= zero or one + * () ::= define a group with "or" semantics. + * + * HSM EBNF: + * HSM = HSM_NAME "{" { STATE }+ "}" ; + * HSM_NAME = alpha_numeric_name ; + * STATE = INTRODUCE_STATE [ ENTER ] "{" [ MESSAGES ] "}" [ EXIT ] ; + * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ; + * STATE_DEPTH = "+" ; + * INITIAL_STATE_INDICATOR = "#" + * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; + * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ; + * MSG_LIST = { MSG_NAME { "," MSG_NAME } ; + * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; + * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ; + * SEND_ACTION = "$" MSG_NAME ; + * DEFER_ACTION = "%" MSG_NAME ; + * TRANSITION_ACTION = ">" STATE_NAME ; + * HALT_ACTION = ">" HALT ; + * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ; + * ACTION_LIST = ACTION { (";" | "\n") ACTION } ; + * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ; + * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}" + * PROCESS_IN_PARENT_OR_COMPLETE = "^" ; + * PROCESS_COMPLETE = "." ; + * STATE_NAME = alpha_numeric_name ; + * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ; + * ALL_OTHER_MESSAGES = "*" ; + * EXP = boolean_expression ; + * + * Idioms: + * * { %* }. ::= All other messages will be deferred. + */ +public class HierarchicalStateMachine { + + private static final String TAG = "HierarchicalStateMachine"; + private String mName; + + private static class HsmHandler extends Handler { + + /** The debug flag */ + private boolean mDbg = false; + + /** A list of messages that this state machine has processed */ + private ProcessedMessages mProcessedMessages = new ProcessedMessages(); + + /** true if construction of the state machine has not been completed */ + private boolean mIsConstructionCompleted; + + /** Stack used to manage the current hierarchy of states */ + private StateInfo mStateStack[]; + + /** Top of mStateStack */ + private int mStateStackTopIndex = -1; + + /** A temporary stack used to manage the state stack */ + private StateInfo mTempStateStack[]; + + /** The top of the mTempStateStack */ + private int mTempStateStackCount; + + /** State used when state machine is halted */ + private HaltingState mHaltingState = new HaltingState(); + + /** Reference to the HierarchicalStateMachine */ + private HierarchicalStateMachine mHsm; + + /** + * Information about a state. + * Used to maintain the hierarchy. + */ + private class StateInfo { + /** The state */ + HierarchicalState state; + + /** The parent of this state, null if there is no parent */ + StateInfo parentStateInfo; + + /** True when the state has been entered and on the stack */ + boolean active; + + /** + * Convert StateInfo to string + */ + @Override + public String toString() { + return "state=" + state.getName() + ",active=" + active + + ",parent=" + ((parentStateInfo == null) ? + "null" : parentStateInfo.state.getName()); + } + } + + /** The map of all of the states in the state machine */ + private HashMap<HierarchicalState, StateInfo> mStateInfo = + new HashMap<HierarchicalState, StateInfo>(); + + /** The initial state that will process the first message */ + private HierarchicalState mInitialState; + + /** The destination state when transitionTo has been invoked */ + private HierarchicalState mDestState; + + /** The list of deferred messages */ + private ArrayList<Message> mDeferredMessages = new ArrayList<Message>(); + + /** + * State entered when transitionToHaltingState is called. + */ + private class HaltingState extends HierarchicalState { + @Override + public boolean processMessage(Message msg) { + mHsm.haltedProcessMessage(msg); + return true; + } + } + + /** + * Handle messages sent to the state machine by calling + * the current state's processMessage. It also handles + * the enter/exit calls and placing any deferred messages + * back onto the queue when transitioning to a new state. + */ + @Override + public final void handleMessage(Message msg) { + if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what); + + /** + * Check that construction was completed + */ + if (!mIsConstructionCompleted) { + Log.e(TAG, "The start method not called, ignore msg: " + msg); + return; + } + + /** + * Process the message abiding by the hierarchical semantics. + */ + processMsg(msg); + + /** + * If transitionTo has been called, exit and then enter + * the appropriate states. + */ + if (mDestState != null) { + if (mDbg) Log.d(TAG, "handleMessage: new destination call exit"); + + /** + * Determine the states to exit and enter and return the + * common ancestor state of the enter/exit states. Then + * invoke the exit methods then the enter methods. + */ + StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(mDestState); + invokeExitMethods(commonStateInfo); + int stateStackEnteringIndex = moveTempStateStackToStateStack(); + invokeEnterMethods(stateStackEnteringIndex); + + + /** + * Since we have transitioned to a new state we need to have + * any deferred messages moved to the front of the message queue + * so they will be processed before any other messages in the + * message queue. + */ + moveDeferredMessageAtFrontOfQueue(); + + /** + * Call halting() if we've transitioned to the halting + * state. All subsequent messages will be processed in + * in the halting state which invokes haltedProcessMessage(msg); + */ + if (mDestState == mHaltingState) { + mHsm.halting(); + } + mDestState = null; + } + + if (mDbg) Log.d(TAG, "handleMessage: X"); + } + + /** + * Complete the construction of the state machine. + */ + private final void completeConstruction() { + if (mDbg) Log.d(TAG, "completeConstruction: E"); + + /** + * Determine the maximum depth of the state hierarchy + * so we can allocate the state stacks. + */ + int maxDepth = 0; + for (StateInfo si : mStateInfo.values()) { + int depth = 0; + for (StateInfo i = si; i != null; depth++) { + i = i.parentStateInfo; + } + if (maxDepth < depth) { + maxDepth = depth; + } + } + if (mDbg) Log.d(TAG, "completeConstruction: maxDepth=" + maxDepth); + + mStateStack = new StateInfo[maxDepth]; + mTempStateStack = new StateInfo[maxDepth]; + setupInitialStateStack(); + + /** + * Construction is complete call all enter methods + * starting at the first entry. + */ + mIsConstructionCompleted = true; + invokeEnterMethods(0); + + if (mDbg) Log.d(TAG, "completeConstruction: X"); + } + + /** + * Process the message. If the current state doesn't handle + * it, call the states parent and so on. If it is never handled then + * call the state machines unhandledMessage method. + */ + private final void processMsg(Message msg) { + StateInfo curStateInfo = mStateStack[mStateStackTopIndex]; + if (mDbg) { + Log.d(TAG, "processMsg: " + curStateInfo.state.getName()); + } + while (!curStateInfo.state.processMessage(msg)) { + /** + * Not processed + */ + curStateInfo = curStateInfo.parentStateInfo; + if (curStateInfo == null) { + /** + * No parents left so it's not handled + */ + mHsm.unhandledMessage(msg); + break; + } + if (mDbg) { + Log.d(TAG, "processMsg: " + curStateInfo.state.getName()); + } + } + + /** + * Record that we processed the message + */ + if (curStateInfo != null) { + HierarchicalState orgState = mStateStack[mStateStackTopIndex].state; + mProcessedMessages.add(msg, curStateInfo.state, orgState); + } else { + mProcessedMessages.add(msg, null, null); + } + } + + /** + * Call the exit method for each state from the top of stack + * up to the common ancestor state. + */ + private final void invokeExitMethods(StateInfo commonStateInfo) { + while ((mStateStackTopIndex >= 0) && + (mStateStack[mStateStackTopIndex] != commonStateInfo)) { + HierarchicalState curState = mStateStack[mStateStackTopIndex].state; + if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName()); + curState.exit(); + mStateStack[mStateStackTopIndex].active = false; + mStateStackTopIndex -= 1; + } + } + + /** + * Invoke the enter method starting at the entering index to top of state stack + */ + private final void invokeEnterMethods(int stateStackEnteringIndex) { + for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) { + if (mDbg) Log.d(TAG, "invokeEnterMethods: " + mStateStack[i].state.getName()); + mStateStack[i].state.enter(); + mStateStack[i].active = true; + } + } + + /** + * Move the deferred message to the front of the message queue. + */ + private final void moveDeferredMessageAtFrontOfQueue() { + /** + * The oldest messages on the deferred list must be at + * the front of the queue so start at the back, which + * as the most resent message and end with the oldest + * messages at the front of the queue. + */ + for (int i = mDeferredMessages.size() - 1; i >= 0; i-- ) { + Message curMsg = mDeferredMessages.get(i); + if (mDbg) Log.d(TAG, "moveDeferredMessageAtFrontOfQueue; what=" + curMsg.what); + sendMessageAtFrontOfQueue(curMsg); + } + mDeferredMessages.clear(); + } + + /** + * Move the contents of the temporary stack to the state stack + * reversing the order of the items on the temporary stack as + * they are moved. + * + * @return index into mStateState where entering needs to start + */ + private final int moveTempStateStackToStateStack() { + int startingIndex = mStateStackTopIndex + 1; + int i = mTempStateStackCount - 1; + int j = startingIndex; + while (i >= 0) { + if (mDbg) Log.d(TAG, "moveTempStackToStateStack: i=" + i + ",j=" + j); + mStateStack[j] = mTempStateStack[i]; + j += 1; + i -= 1; + } + + mStateStackTopIndex = j - 1; + if (mDbg) { + Log.d(TAG, "moveTempStackToStateStack: X mStateStackTop=" + + mStateStackTopIndex + ",startingIndex=" + startingIndex + + ",Top=" + mStateStack[mStateStackTopIndex].state.getName()); + } + return startingIndex; + } + + /** + * Setup the mTempStateStack with the states we are going to enter. + * + * This is found by searching up the destState's ancestors for a + * state that is already active i.e. StateInfo.active == true. + * The destStae and all of its inactive parents will be on the + * TempStateStack as the list of states to enter. + * + * @return StateInfo of the common ancestor for the destState and + * current state or null if there is no common parent. + */ + private final StateInfo setupTempStateStackWithStatesToEnter(HierarchicalState destState) { + /** + * Search up the parent list of the destination state for an active + * state. Use a do while() loop as the destState must always be entered + * even if it is active. This can happen if we are exiting/entering + * the current state. + */ + mTempStateStackCount = 0; + StateInfo curStateInfo = mStateInfo.get(destState); + do { + mTempStateStack[mTempStateStackCount++] = curStateInfo; + curStateInfo = curStateInfo.parentStateInfo; + } while ((curStateInfo != null) && !curStateInfo.active); + + if (mDbg) { + Log.d(TAG, "setupTempStateStackWithStatesToEnter: X mTempStateStackCount=" + + mTempStateStackCount + ",curStateInfo: " + curStateInfo); + } + return curStateInfo; + } + + /** + * Initialize StateStack to mInitialState. + */ + private final void setupInitialStateStack() { + if (mDbg) { + Log.d(TAG, "setupInitialStateStack: E mInitialState=" + + mInitialState.getName()); + } + + StateInfo curStateInfo = mStateInfo.get(mInitialState); + for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) { + mTempStateStack[mTempStateStackCount] = curStateInfo; + curStateInfo = curStateInfo.parentStateInfo; + } + + // Empty the StateStack + mStateStackTopIndex = -1; + + moveTempStateStackToStateStack(); + } + + /** + * @return current state + */ + private final HierarchicalState getCurrentState() { + return mStateStack[mStateStackTopIndex].state; + } + + /** + * Add a new state to the state machine. Bottom up addition + * of states is allowed but the same state may only exist + * in one hierarchy. + * + * @param state the state to add + * @param parent the parent of state + * @return stateInfo for this state + */ + private final StateInfo addState(HierarchicalState state, HierarchicalState parent) { + if (mDbg) { + Log.d(TAG, "addStateInternal: E state=" + state.getName() + + ",parent=" + ((parent == null) ? "" : parent.getName())); + } + StateInfo parentStateInfo = null; + if (parent != null) { + parentStateInfo = mStateInfo.get(parent); + if (parentStateInfo == null) { + // Recursively add our parent as it's not been added yet. + parentStateInfo = addState(parent, null); + } + } + StateInfo stateInfo = mStateInfo.get(state); + if (stateInfo == null) { + stateInfo = new StateInfo(); + mStateInfo.put(state, stateInfo); + } + + // Validate that we aren't adding the same state in two different hierarchies. + if ((stateInfo.parentStateInfo != null) && + (stateInfo.parentStateInfo != parentStateInfo)) { + throw new RuntimeException("state already added"); + } + stateInfo.state = state; + stateInfo.parentStateInfo = parentStateInfo; + stateInfo.active = false; + if (mDbg) Log.d(TAG, "addStateInternal: X stateInfo: " + stateInfo); + return stateInfo; + } + + /** + * Constructor + * + * @param looper for dispatching messages + * @param hsm the hierarchical state machine + */ + private HsmHandler(Looper looper, HierarchicalStateMachine hsm) { + super(looper); + mHsm = hsm; + + addState(mHaltingState, null); + } + + /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */ + private final void setInitialState(HierarchicalState initialState) { + if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName()); + mInitialState = initialState; + } + + /** @see HierarchicalStateMachine#transitionTo(HierarchicalState) */ + private final void transitionTo(HierarchicalState destState) { + if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + destState.getName()); + mDestState = destState; + } + + /** @see HierarchicalStateMachine#deferMessage(Message) */ + private final void deferMessage(Message msg) { + if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what); + + /* Copy the "msg" to "newMsg" as "msg" will be recycled */ + Message newMsg = obtainMessage(); + newMsg.copyFrom(msg); + + mDeferredMessages.add(newMsg); + } + + /** @see HierarchicalStateMachine#isDbg() */ + private final boolean isDbg() { + return mDbg; + } + + /** @see HierarchicalStateMachine#setDbg(boolean) */ + private final void setDbg(boolean dbg) { + mDbg = dbg; + } + + /** @see HierarchicalStateMachine#setProcessedMessagesSize(int) */ + private final void setProcessedMessagesSize(int maxSize) { + mProcessedMessages.setSize(maxSize); + } + + /** @see HierarchicalStateMachine#getProcessedMessagesSize() */ + private final int getProcessedMessagesSize() { + return mProcessedMessages.size(); + } + + /** @see HierarchicalStateMachine#getProcessedMessagesCount() */ + private final int getProcessedMessagesCount() { + return mProcessedMessages.count(); + } + + /** @see HierarchicalStateMachine#getProcessedMessage(int) */ + private final ProcessedMessages.Info getProcessedMessage(int index) { + return mProcessedMessages.get(index); + } + + } + + private HsmHandler mHsmHandler; + private HandlerThread mHsmThread; + + /** + * Initialize. + * + * @param looper for this state machine + * @param name of the state machine + */ + private void initStateMachine(Looper looper, String name) { + mName = name; + mHsmHandler = new HsmHandler(looper, this); + } + + /** + * Constructor creates an HSM with its own thread. + * + * @param name of the state machine + */ + protected HierarchicalStateMachine(String name) { + mHsmThread = new HandlerThread(name); + mHsmThread.start(); + Looper looper = mHsmThread.getLooper(); + + initStateMachine(looper, name); + } + + /** + * Constructor creates an HSMStateMachine using the looper. + * + * @param name of the state machine + */ + protected HierarchicalStateMachine(Looper looper, String name) { + initStateMachine(looper, name); + } + + /** + * Add a new state to the state machine + * @param state the state to add + * @param parent the parent of state + */ + protected final void addState(HierarchicalState state, HierarchicalState parent) { + mHsmHandler.addState(state, parent); + } + /** + * @return current state + */ + protected final HierarchicalState getCurrentState() { + return mHsmHandler.getCurrentState(); + } + + + /** + * Add a new state to the state machine, parent will be null + * @param state to add + */ + protected final void addState(HierarchicalState state) { + mHsmHandler.addState(state, null); + } + + /** + * Set the initial state. This must be invoked before + * and messages are sent to the state machine. + * + * @param initialState is the state which will receive the first message. + */ + protected final void setInitialState(HierarchicalState initialState) { + mHsmHandler.setInitialState(initialState); + } + + /** + * transition to destination state. Upon returning + * from processMessage the current state's exit will + * be executed and upon the next message arriving + * destState.enter will be invoked. + * + * @param destState will be the state that receives the next message. + */ + protected final void transitionTo(HierarchicalState destState) { + mHsmHandler.transitionTo(destState); + } + + /** + * transition to halt state. Upon returning + * from processMessage we will exit all current + * states, execute the halting() method and then + * all subsequent messages haltedProcessMesage + * will be called. + */ + protected final void transitionToHaltingState() { + mHsmHandler.transitionTo(mHsmHandler.mHaltingState); + } + + /** + * Defer this message until next state transition. + * Upon transitioning all deferred messages will be + * placed on the queue and reprocessed in the original + * order. (i.e. The next state the oldest messages will + * be processed first) + * + * @param msg is deferred until the next transition. + */ + protected final void deferMessage(Message msg) { + mHsmHandler.deferMessage(msg); + } + + + /** + * Called when message wasn't handled + * + * @param msg that couldn't be handled. + */ + protected void unhandledMessage(Message msg) { + Log.e(TAG, "unhandledMessage: msg.what=" + msg.what); + } + + /** + * Called for any message that is received after + * transitionToHalting is called. + */ + protected void haltedProcessMessage(Message msg) { + } + + /** + * Called after the message that called transitionToHalting + * is called and should be overridden by StateMachine's that + * call transitionToHalting. + */ + protected void halting() { + } + + /** + * @return the name + */ + public final String getName() { + return mName; + } + + /** + * Set size of messages to maintain and clears all current messages. + * + * @param maxSize number of messages to maintain at anyone time. + */ + public final void setProcessedMessagesSize(int maxSize) { + mHsmHandler.setProcessedMessagesSize(maxSize); + } + + /** + * @return number of messages processed + */ + public final int getProcessedMessagesSize() { + return mHsmHandler.getProcessedMessagesSize(); + } + + /** + * @return the total number of messages processed + */ + public final int getProcessedMessagesCount() { + return mHsmHandler.getProcessedMessagesCount(); + } + + /** + * @return a processed message + */ + public final ProcessedMessages.Info getProcessedMessage(int index) { + return mHsmHandler.getProcessedMessage(index); + } + + /** + * @return Handler + */ + public final Handler getHandler() { + return mHsmHandler; + } + + /** + * Get a message and set Message.target = this. + * + * @return message + */ + public final Message obtainMessage() + { + return Message.obtain(mHsmHandler); + } + + /** + * Get a message and set Message.target = this and what + * + * @param what is the assigned to Message.what. + * @return message + */ + public final Message obtainMessage(int what) { + return Message.obtain(mHsmHandler, what); + } + + /** + * Get a message and set Message.target = this, + * what and obj. + * + * @param what is the assigned to Message.what. + * @param obj is assigned to Message.obj. + * @return message + */ + public final Message obtainMessage(int what, Object obj) + { + return Message.obtain(mHsmHandler, what, obj); + } + + /** + * Enqueue a message to this state machine. + */ + public final void sendMessage(Message msg) { + mHsmHandler.sendMessage(msg); + } + + /** + * Enqueue a message to this state machine after a delay. + */ + public final void sendMessageDelayed(Message msg, long delayMillis) { + mHsmHandler.sendMessageDelayed(msg, delayMillis); + } + + /** + * Enqueue a message to the front of the queue for this state machine. + * Protected, may only be called by instances of HierarchicalStateMachine. + */ + protected final void sendMessageAtFrontOfQueue(Message msg) { + mHsmHandler.sendMessageAtFrontOfQueue(msg); + } + + /** + * @return if debugging is enabled + */ + public boolean isDbg() { + return mHsmHandler.isDbg(); + } + + /** + * Set debug enable/disabled. + * + * @param dbg is true to enable debugging. + */ + public void setDbg(boolean dbg) { + mHsmHandler.setDbg(dbg); + } + + /** + * Start the state machine. + */ + public void start() { + /** Send the complete construction message */ + mHsmHandler.completeConstruction(); + } +} diff --git a/core/java/com/android/internal/util/ProcessedMessages.java b/core/java/com/android/internal/util/ProcessedMessages.java new file mode 100644 index 0000000..244474e --- /dev/null +++ b/core/java/com/android/internal/util/ProcessedMessages.java @@ -0,0 +1,198 @@ +/** + * Copyright (C) 2009 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.internal.util; + +import android.os.Message; + +import java.util.Vector; + +/** + * {@hide} + * + * A list of messages recently processed by the state machine. + * + * The class maintains a list of messages that have been most + * recently processed. The list is finite and may be set in the + * constructor or by calling setSize. The public interface also + * includes size which returns the number of recent messages, + * count which is the number of message processed since the + * the last setSize, get which returns a processed message and + * add which adds a processed messaged. + */ +public class ProcessedMessages { + + public static final int DEFAULT_SIZE = 20; + + /** + * The information maintained for a processed message. + */ + public class Info { + private int what; + private HierarchicalState state; + private HierarchicalState orgState; + + /** + * Constructor + * @param message + * @param state that handled the message + * @param orgState is the first state the received the message but + * did not processes the message. + */ + Info(Message message, HierarchicalState state, HierarchicalState orgState) { + update(message, state, orgState); + } + + /** + * Update the information in the record. + * @param state that handled the message + * @param orgState is the first state the received the message but + * did not processes the message. + */ + public void update(Message message, HierarchicalState state, HierarchicalState orgState) { + this.what = message.what; + this.state = state; + this.orgState = orgState; + } + + /** + * @return the command that was executing + */ + public int getWhat() { + return what; + } + + /** + * @return the state that handled this message + */ + public HierarchicalState getState() { + return state; + } + + /** + * @return the original state that received the message. + */ + public HierarchicalState getOriginalState() { + return orgState; + } + + /** + * @return as string + */ + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("what="); + sb.append(what); + sb.append(" state="); + sb.append(cn(state)); + sb.append(" orgState="); + sb.append(cn(orgState)); + return sb.toString(); + } + + /** + * @return an objects class name + */ + private String cn(Object n) { + if (n == null) { + return "null"; + } else { + String name = n.getClass().getName(); + int lastDollar = name.lastIndexOf('$'); + return name.substring(lastDollar + 1); + } + } + } + + private Vector<Info> mMessages = new Vector<Info>(); + private int mMaxSize = DEFAULT_SIZE; + private int mOldestIndex = 0; + private int mCount = 0; + + /** + * Constructor + */ + ProcessedMessages() { + } + + ProcessedMessages(int maxSize) { + setSize(maxSize); + } + + /** + * Set size of messages to maintain and clears all current messages. + * + * @param maxSize number of messages to maintain at anyone time. + */ + void setSize(int maxSize) { + mMaxSize = maxSize; + mCount = 0; + mMessages.clear(); + } + + /** + * @return the number of recent messages. + */ + int size() { + return mMessages.size(); + } + + /** + * @return the total number of messages processed since size was set. + */ + int count() { + return mCount; + } + + /** + * @return the information on a particular record. 0 is the oldest + * record and size()-1 is the newest record. If the index is to + * large null is returned. + */ + Info get(int index) { + int nextIndex = mOldestIndex + index; + if (nextIndex >= mMaxSize) { + nextIndex -= mMaxSize; + } + if (nextIndex >= size()) { + return null; + } else { + return mMessages.get(nextIndex); + } + } + + /** + * Add a processed message. + * + * @param message + * @param state that handled the message + * @param orgState is the first state the received the message but + * did not processes the message. + */ + void add(Message message, HierarchicalState state, HierarchicalState orgState) { + mCount += 1; + if (mMessages.size() < mMaxSize) { + mMessages.add(new Info(message, state, orgState)); + } else { + Info info = mMessages.get(mOldestIndex); + mOldestIndex += 1; + if (mOldestIndex >= mMaxSize) { + mOldestIndex = 0; + } + info.update(message, state, orgState); + } + } +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 3683aab..9e72f64 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1844,8 +1844,6 @@ <string name="report">Report</string> <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. --> <string name="wait">Wait</string> - <!-- Button allowing a developer to connect a debugger to an application that is not responding. --> - <string name="debug">Debug</string> <!-- Displayed in the title of the chooser for things to do with text that is to be sent to another application. For example, I can send text through SMS or IM. A dialog with those choices would be shown, and this would be the title. --> |