diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-01-22 00:13:42 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-01-22 00:13:42 -0800 |
commit | f1e484acb594a726fb57ad0ae4cfe902c7f35858 (patch) | |
tree | 99d2b34512f0dc2ae67666e756c1cfcd331e5fe3 /core/java/android | |
parent | 22f7dfd23490a3de2f21ff96949ba47003aac8f8 (diff) | |
download | frameworks_base-f1e484acb594a726fb57ad0ae4cfe902c7f35858.zip frameworks_base-f1e484acb594a726fb57ad0ae4cfe902c7f35858.tar.gz frameworks_base-f1e484acb594a726fb57ad0ae4cfe902c7f35858.tar.bz2 |
auto import from //branches/cupcake/...@127436
Diffstat (limited to 'core/java/android')
36 files changed, 777 insertions, 336 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c98cf1b..4dc4b6a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1132,6 +1132,17 @@ public class Activity extends ContextThemeWrapper } /** + * Called as part of the activity lifecycle when an activity is about to go + * into the background as the result of user choice. For example, when the + * user presses the Home key, {@link #onUserLeaving} will be called, but + * when an incoming phone call causes the in-call Activity to be automatically + * brought to the foreground, {@link #onUserLeaving} will not be called on + * the activity being interrupted. + */ + protected void onUserLeaving() { + } + + /** * Generate a new thumbnail for this activity. This method is called before * pausing the activity, and should draw into <var>outBitmap</var> the * imagery for the desired thumbnail in the dimensions of that bitmap. It @@ -3470,6 +3481,10 @@ public class Activity extends ContextThemeWrapper onPause(); } + final void performUserLeaving() { + onUserLeaving(); + } + final void performStop() { if (!mStopped) { if (mWindow != null) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index a98e295..e4c1057 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1207,10 +1207,12 @@ public final class ActivityThread { private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d"; public final void schedulePauseActivity(IBinder token, boolean finished, - int configChanges) { + boolean userLeaving, int configChanges) { queueOrSendMessage( finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY, - token, configChanges); + token, + (userLeaving ? 1 : 0), + configChanges); } public final void scheduleStopActivity(IBinder token, boolean showWindow, @@ -1588,10 +1590,10 @@ public final class ActivityThread { handleRelaunchActivity(r, msg.arg1); } break; case PAUSE_ACTIVITY: - handlePauseActivity((IBinder)msg.obj, false, msg.arg2); + handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); break; case PAUSE_ACTIVITY_FINISHING: - handlePauseActivity((IBinder)msg.obj, true, msg.arg2); + handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); break; case STOP_ACTIVITY_SHOW: handleStopActivity((IBinder)msg.obj, true, msg.arg2); @@ -2647,9 +2649,14 @@ public final class ActivityThread { } private final void handlePauseActivity(IBinder token, boolean finished, - int configChanges) { + boolean userLeaving, int configChanges) { ActivityRecord r = mActivities.get(token); if (r != null) { + //Log.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); + if (userLeaving) { + performUserLeavingActivity(r); + } + r.activity.mConfigChangeFlags |= configChanges; Bundle state = performPauseActivity(token, finished, true); @@ -2661,6 +2668,10 @@ public final class ActivityThread { } } + final void performUserLeavingActivity(ActivityRecord r) { + mInstrumentation.callActivityOnUserLeaving(r.activity); + } + final Bundle performPauseActivity(IBinder token, boolean finished, boolean saveState) { ActivityRecord r = mActivities.get(token); diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 0e41ae6..4236a00 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -315,8 +315,13 @@ class ApplicationContext extends Context { backup.renameTo(f); } + // Debugging + if (f.exists() && !f.canRead()) { + Log.w(TAG, "Attempt to read preferences file " + f + " without permission"); + } + Map map = null; - if (f.exists()) { + if (f.exists() && f.canRead()) { try { str = new FileInputStream(f); map = XmlUtils.readMapXml(str); @@ -2264,14 +2269,23 @@ class ApplicationContext extends Context { } } @Override - public void freeApplicationCache(long idealStorageSize, - IPackageDataObserver observer) { + public void freeStorageAndNotify(long idealStorageSize, IPackageDataObserver observer) { try { - mPM.freeApplicationCache(idealStorageSize, observer); + mPM.freeStorageAndNotify(idealStorageSize, observer); } catch (RemoteException e) { // Should never happen! } } + + @Override + public void freeStorage(long idealStorageSize, PendingIntent opFinishedIntent) { + try { + mPM.freeStorage(idealStorageSize, opFinishedIntent); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override public void getPackageSizeInfo(String packageName, IPackageStatsObserver observer) { diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index 54237ae..d2cf55a 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -69,8 +69,9 @@ public abstract class ApplicationThreadNative extends Binder data.enforceInterface(IApplicationThread.descriptor); IBinder b = data.readStrongBinder(); boolean finished = data.readInt() != 0; + boolean userLeaving = data.readInt() != 0; int configChanges = data.readInt(); - schedulePauseActivity(b, finished, configChanges); + schedulePauseActivity(b, finished, userLeaving, configChanges); return true; } @@ -344,11 +345,12 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void schedulePauseActivity(IBinder token, boolean finished, - int configChanges) throws RemoteException { + boolean userLeaving, int configChanges) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); data.writeInt(finished ? 1 : 0); + data.writeInt(userLeaving ? 1 :0); data.writeInt(configChanges); mRemote.transact(SCHEDULE_PAUSE_ACTIVITY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index a351581..47476b5 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -40,7 +40,7 @@ import java.util.Map; * {@hide} */ public interface IApplicationThread extends IInterface { - void schedulePauseActivity(IBinder token, boolean finished, + void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges) throws RemoteException; void scheduleStopActivity(IBinder token, boolean showWindow, int configChanges) throws RemoteException; diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 17618ff..f96d787 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1266,6 +1266,16 @@ public class Instrumentation { activity.performPause(); } + /** + * Perform calling of an activity's {@link Activity#onUserLeaving} method. + * The default implementation simply calls through to that method. + * + * @param activity The activity being notified that the user has navigated away + */ + public void callActivityOnUserLeaving(Activity activity) { + activity.performUserLeaving(); + } + /* * Starts allocation counting. This triggers a gc and resets the counts. */ diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 5af08f7..2e2a1a1 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -222,17 +222,27 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // configure the autocomplete aspects of the input box mSearchTextField.setOnItemClickListener(this); mSearchTextField.setOnItemSelectedListener(this); - - // attach the suggestions adapter - mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable); - mSearchTextField.setAdapter(mSuggestionsAdapter); - // finally, load the user's initial text (which may trigger suggestions) - mSuggestionsAdapter.setNonUserQuery(false); + // This conversion is necessary to force a preload of the EditText and thus force + // suggestions to be presented (even for an empty query) if (initialQuery == null) { initialQuery = ""; // This forces the preload to happen, triggering suggestions } - mSearchTextField.setText(initialQuery); + + // attach the suggestions adapter, if suggestions are available + // The existence of a suggestions authority is the proxy for "suggestions available here" + if (mSearchable.getSuggestAuthority() == null) { + mSuggestionsAdapter = null; + mSearchTextField.setAdapter(mSuggestionsAdapter); + mSearchTextField.setText(initialQuery); + } else { + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable); + mSearchTextField.setAdapter(mSuggestionsAdapter); + + // finally, load the user's initial text (which may trigger suggestions) + mSuggestionsAdapter.setNonUserQuery(false); + mSearchTextField.setText(initialQuery); + } if (selectInitialQuery) { mSearchTextField.selectAll(); @@ -344,7 +354,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS return; } - mSuggestionsAdapter.setNonUserQuery(true); + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.setNonUserQuery(true); + } mSearchTextField.setText(displayQuery); // TODO because the new query is (not) processed in another thread, we can't just // take away this flag (yet). The better solution here is going to require a new API @@ -539,7 +551,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } updateWidgetState(); // Only do suggestions if actually typed by user - if (!mSuggestionsAdapter.getNonUserQuery()) { + if ((mSuggestionsAdapter != null) && !mSuggestionsAdapter.getNonUserQuery()) { mPreviousSuggestionQuery = s.toString(); mUserQuery = mSearchTextField.getText().toString(); mUserQuerySelStart = mSearchTextField.getSelectionStart(); @@ -822,6 +834,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * React to the user typing an action key while in the suggestions list */ private boolean doSuggestionsKey(View v, int keyCode, KeyEvent event) { + // Exit early in case of race condition + if (mSuggestionsAdapter == null) { + return false; + } if (event.getAction() == KeyEvent.ACTION_DOWN) { if (DBG_LOG_TIMING == 1) { dbgLogTiming("doSuggestionsKey()"); diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index d1f71c5..d613e1c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -39,6 +39,28 @@ public class BluetoothDevice { public static final int RESULT_FAILURE = -1; public static final int RESULT_SUCCESS = 0; + /** We do not have a link key for the remote device, and are therefore not + * bonded */ + public static final int BOND_NOT_BONDED = 0; + /** We have a link key for the remote device, and are probably bonded. */ + public static final int BOND_BONDED = 1; + /** We are currently attempting bonding */ + public static final int BOND_BONDING = 2; + + //TODO: Unify these result codes in BluetoothResult or BluetoothError + /** A bond attempt failed because pins did not match, or remote device did + * not respond to pin request in time */ + public static final int UNBOND_REASON_AUTH_FAILED = 1; + /** A bond attempt failed because the other side explicilty rejected + * bonding */ + public static final int UNBOND_REASON_AUTH_REJECTED = 2; + /** A bond attempt failed because we cancelled the bonding process */ + public static final int UNBOND_REASON_CANCELLED = 3; + /** A bond attempt failed because we could not contact the remote device */ + public static final int UNBOND_REASON_AUTH_REMOTE_DEVICE_DOWN = 4; + /** An existing bond was explicitly revoked */ + public static final int UNBOND_REASON_REMOVED = 5; + private static final String TAG = "BluetoothDevice"; private final IBluetoothDevice mService; @@ -325,42 +347,36 @@ public class BluetoothDevice { /** * Create a bonding with a remote bluetooth device. * - * This is an asynchronous call. BluetoothIntent.BONDING_CREATED_ACTION - * will be broadcast if and when the remote device is successfully bonded. + * This is an asynchronous call. The result of this bonding attempt can be + * observed through BluetoothIntent.BOND_STATE_CHANGED_ACTION intents. * * @param address the remote device Bluetooth address. - * @return false if we cannot create a bonding to that device, true if - * there were no problems beginning the bonding process. + * @return false If there was an immediate problem creating the bonding, + * true otherwise. */ - public boolean createBonding(String address) { - return createBonding(address, null); + public boolean createBond(String address) { + try { + return mService.createBond(address); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; } /** - * Create a bonding with a remote bluetooth device. - * - * This is an asynchronous call. onCreateBondingResult() of your callback - * will be called when the call is complete, with either RESULT_SUCCESS or - * RESULT_FAILURE. - * - * In addition to the callback, BluetoothIntent.BONDING_CREATED_ACTION will - * be broadcast if the remote device is successfully bonded. - * - * @param address The remote device Bluetooth address. - * @param callback Your callback, null is ok. - * @return true if your callback was successfully registered, or false if - * there was an error, implying your callback will never be called. + * Cancel an in-progress bonding request started with createBond. */ - public boolean createBonding(String address, IBluetoothDeviceCallback callback) { + public boolean cancelBondProcess(String address) { try { - return mService.createBonding(address, callback); + return mService.cancelBondProcess(address); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } - public boolean cancelBondingProcess(String address) { + /** + * Remove an already exisiting bonding (delete the link key). + */ + public boolean removeBond(String address) { try { - return mService.cancelBondingProcess(address); + return mService.removeBond(address); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -382,48 +398,34 @@ public class BluetoothDevice { * * This function does not check if the remote device is in range. * + * Remote devices that have an in-progress bonding attempt are not + * returned. + * * @return bluetooth hardware addresses of remote devices that are * bonded. Array size is 0 if no devices are bonded. Null on error. */ - public String[] listBondings() { + public String[] listBonds() { try { - return mService.listBondings(); + return mService.listBonds(); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } /** - * Check if a remote device is bonded (paired) to the local device. + * Get the bonding state of a remote device. * - * Bonding (pairing) is the process by which the user enters a pin code for - * the device, which generates a shared link key, allowing for - * authentication and encryption of future connections. In Android we - * require bonding before RFCOMM or SCO connections can be made to a remote - * device. - * - * This function checks if we have a link key with the remote device. It - * does not cause any RF transmission, and does not check if the remote - * device still has it's link key with us. If the other side no longer has - * a link key then the RFCOMM or SCO connection attempt will result in an - * error. - * - * This function does not check if the remote device is in range. + * Result is one of: + * BluetoothError.* + * BOND_* * * @param address Bluetooth hardware address of the remote device to check. - * @return true if bonded, false otherwise and on error. + * @return Result code */ - public boolean hasBonding(String address) { + public int getBondState(String address) { try { - return mService.hasBonding(address); + return mService.getBondState(address); } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } - - public boolean removeBonding(String address) { - try { - return mService.removeBonding(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + return BluetoothError.ERROR_IPC; } public String getRemoteName(String address) { diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java index 8e22791..57c46f9 100644 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -45,6 +45,12 @@ public interface BluetoothIntent { "android.bluetooth.intent.HEADSET_STATE"; public static final String HEADSET_PREVIOUS_STATE = "android.bluetooth.intent.HEADSET_PREVIOUS_STATE"; + public static final String BOND_STATE = + "android.bluetooth.intent.BOND_STATE"; + public static final String BOND_PREVIOUS_STATE = + "android.bluetooth.intent.BOND_PREVIOUS_STATE"; + public static final String REASON = + "android.bluetooth.intent.REASON"; @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ENABLED_ACTION = @@ -105,12 +111,17 @@ public interface BluetoothIntent { public static final String REMOTE_ALIAS_CLEARED_ACTION = "android.bluetooth.intent.action.REMOTE_ALIAS_CLEARED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String BONDING_CREATED_ACTION = - "android.bluetooth.intent.action.BONDING_CREATED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String BONDING_REMOVED_ACTION = - "android.bluetooth.intent.action.BONDING_REMOVED"; + /** + * Broadcast when the bond state of a remote device changes. + * Has string extra ADDRESS and int extras BOND_STATE and + * BOND_PREVIOUS_STATE. + * If BOND_STATE is BluetoothDevice.BOND_NOT_BONDED then will + * also have an int extra REASON with a value of: + * BluetoothDevice.BOND_RESULT_* + * */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String BOND_STATE_CHANGED_ACTION = + "android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION"; @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String HEADSET_STATE_CHANGED_ACTION = diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl index e7cc8ed..59f679f 100644 --- a/core/java/android/bluetooth/IBluetoothDevice.aidl +++ b/core/java/android/bluetooth/IBluetoothDevice.aidl @@ -57,11 +57,11 @@ interface IBluetoothDevice boolean isAclConnected(in String address); boolean disconnectRemoteDeviceAcl(in String address); - boolean createBonding(in String address, in IBluetoothDeviceCallback callback); - boolean cancelBondingProcess(in String address); - String[] listBondings(); - boolean hasBonding(in String address); - boolean removeBonding(in String address); + boolean createBond(in String address); + boolean cancelBondProcess(in String address); + boolean removeBond(in String address); + String[] listBonds(); + int getBondState(in String address); String getRemoteName(in String address); String getRemoteAlias(in String address); diff --git a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl index 86f44dd..d25bd56 100644 --- a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl @@ -21,7 +21,6 @@ package android.bluetooth; */ oneway interface IBluetoothDeviceCallback { - void onCreateBondingResult(in String address, int result); void onGetRemoteServiceChannelResult(in String address, int channel); void onEnableResult(int result); diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4a92b4c..e2d3576 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1225,6 +1225,22 @@ public class Intent implements Parcelable { public static final String ACTION_MEDIA_UNMOUNTED = "android.intent.action.MEDIA_UNMOUNTED"; /** + * Broadcast Action: External media is present, and being disk-checked + * The path to the mount point for the checking media is contained in the Intent.mData field. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_CHECKING = "android.intent.action.MEDIA_CHECKING"; + + /** + * Broadcast Action: External media is present, but is using an incompatible fs (or is blank) + * The path to the mount point for the checking media is contained in the Intent.mData field. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MEDIA_NOFS = "android.intent.action.MEDIA_NOFS"; + + /** * Broadcast Action: External media is present and mounted at its mount point. * The path to the mount point for the removed media is contained in the Intent.mData field. * The Intent contains an extra with name "read-only" and Boolean value to indicate if the @@ -1838,6 +1854,23 @@ public class Intent implements Parcelable { * to mail. */ public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000; + /** + * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaving} + * callback from occurring on the current frontmost activity before it is + * paused as the newly-started activity is brought to the front. + * + * <p>Typically, an activity can rely on that callback to indicate that an + * explicit user action has caused their activity to be moved out of the + * foreground. The callback marks an appropriate point in the activity's + * lifecycle for it to dismiss any notifications that it intends to display + * "until the user has seen them," such as a blinking LED. + * + * <p>If an activity is ever started via any non-user-driven events such as + * phone-call receipt or an alarm handler, this flag should be passed to {@link + * Context#startActivity Context.startActivity}, ensuring that the pausing + * activity does not think the user has acknowledged its notification. + */ + public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000; /** * If set, when sending a broadcast only registered receivers will be @@ -3836,6 +3869,7 @@ public class Intent implements Parcelable { * @see #FLAG_ACTIVITY_MULTIPLE_TASK * @see #FLAG_ACTIVITY_NEW_TASK * @see #FLAG_ACTIVITY_NO_HISTORY + * @see #FLAG_ACTIVITY_NO_USER_ACTION * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED * @see #FLAG_ACTIVITY_SINGLE_TOP diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fdb2a2f..ea86188 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -34,6 +34,7 @@ import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; +import android.app.PendingIntent; /** * See {@link PackageManager} for documentation on most of the APIs @@ -184,22 +185,51 @@ interface IPackageManager { int getApplicationEnabledSetting(in String packageName); /** - * Free storage by deleting LRU sorted list of cache files across all applications. - * If the currently available free storage on the device is greater than or equal to the - * requested free storage, no cache files are cleared. If the currently available storage on the - * device is less than the requested free storage, some or all of the cache files across - * all applications are deleted(based on last accessed time) to increase the free storage - * space on the device to the requested value. There is no gurantee that clearing all - * the cache files from all applications will clear up enough storage to achieve the desired - * value. - * @param freeStorageSize The number of bytes of storage to be - * freed by the system. Say if freeStorageSize is XX, - * and the current free storage is YY, - * if XX is less than YY, just return. if not free XX-YY number of - * bytes if possible. - * @param observer callback used to notify when the operation is completed + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. + * @param freeStorageSize The number of bytes of storage to be + * freed by the system. Say if freeStorageSize is XX, + * and the current free storage is YY, + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param observer call back used to notify when + * the operation is completed */ - void freeApplicationCache(in long freeStorageSize, IPackageDataObserver observer); + void freeStorageAndNotify(in long freeStorageSize, + IPackageDataObserver observer); + + /** + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. + * @param freeStorageSize The number of bytes of storage to be + * freed by the system. Say if freeStorageSize is XX, + * and the current free storage is YY, + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param opFinishedIntent PendingIntent call back used to + * notify when the operation is completed.May be null + * to indicate that no call back is desired. + */ + void freeStorage(in long freeStorageSize, + in PendingIntent opFinishedIntent); /** * Delete all the cache files in an applications cache directory diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index a544550..4b902e9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -17,6 +17,7 @@ package android.content.pm; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -1357,28 +1358,53 @@ public abstract class PackageManager { IPackageDataObserver observer); /** - * Free storage by deleting LRU sorted list of cache files across all applications. - * If the currently available free storage on the device is greater than or equal to the - * requested free storage, no cache files are cleared. If the currently available storage on the - * device is less than the requested free storage, some or all of the cache files across - * all applications are deleted(based on last accessed time) to increase the free storage - * space on the device to the requested value. There is no gurantee that clearing all - * the cache files from all applications will clear up enough storage to achieve the desired - * value. + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. * @param freeStorageSize The number of bytes of storage to be * freed by the system. Say if freeStorageSize is XX, * and the current free storage is YY, - * if XX is less than YY, just return. if not free XX-YY number of - * bytes if possible. - * @param observer callback used to notify when the operation is completed - * {@link android.content.pm.IPackageDataObserver#onRemoveCompleted(String, boolean)} - * will be called when that happens. observer may be null to indicate that - * no callback is desired. - * + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param observer call back used to notify when + * the operation is completed + * * @hide */ - public abstract void freeApplicationCache(long freeStorageSize, - IPackageDataObserver observer); + public abstract void freeStorageAndNotify(long freeStorageSize, IPackageDataObserver observer); + + /** + * Free storage by deleting LRU sorted list of cache files across + * all applications. If the currently available free storage + * on the device is greater than or equal to the requested + * free storage, no cache files are cleared. If the currently + * available storage on the device is less than the requested + * free storage, some or all of the cache files across + * all applications are deleted (based on last accessed time) + * to increase the free storage space on the device to + * the requested value. There is no guarantee that clearing all + * the cache files from all applications will clear up + * enough storage to achieve the desired value. + * @param freeStorageSize The number of bytes of storage to be + * freed by the system. Say if freeStorageSize is XX, + * and the current free storage is YY, + * if XX is less than YY, just return. if not free XX-YY number + * of bytes if possible. + * @param opFinishedIntent PendingIntent call back used to + * notify when the operation is completed.May be null + * to indicate that no call back is desired. + * + * @hide + */ + public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent); /** * Retrieve the size information for a package. diff --git a/core/java/android/gadget/GadgetManager.java b/core/java/android/gadget/GadgetManager.java index 49c706e..088dc86 100644 --- a/core/java/android/gadget/GadgetManager.java +++ b/core/java/android/gadget/GadgetManager.java @@ -54,17 +54,22 @@ public class GadgetManager { /** * Sent when it is time to update your gadget. */ - public static final String GADGET_UPDATE_ACTION = "android.gadget.GADGET_UPDATE"; + public static final String GADGET_UPDATE_ACTION = "android.gadget.action.GADGET_UPDATE"; /** * Sent when the gadget is added to a host for the first time. TODO: Maybe we don't want this. */ - public static final String GADGET_ENABLE_ACTION = "android.gadget.GADGET_ENABLE"; + public static final String GADGET_ENABLE_ACTION = "android.gadget.action.GADGET_ENABLE"; /** * Sent when the gadget is removed from the last host. TODO: Maybe we don't want this. */ - public static final String GADGET_DISABLE_ACTION = "android.gadget.GADGET_DISABLE"; + public static final String GADGET_DISABLE_ACTION = "android.gadget.action.GADGET_DISABLE"; + + /** + * Field for the manifest meta-data tag. + */ + public static final String GADGET_PROVIDER_META_DATA = "android.gadget.provider"; static WeakHashMap<Context, WeakReference<GadgetManager>> sManagerCache = new WeakHashMap(); static IGadgetService sService; @@ -108,10 +113,15 @@ public class GadgetManager { } /** - * Return a list of the gadgets that are currently installed. + * Return a list of the gadget providers that are currently installed. */ - public List<GadgetInfo> getAvailableGadgets() { - return null; + public List<GadgetInfo> getInstalledProviders() { + try { + return sService.getInstalledProviders(); + } + catch (RemoteException e) { + throw new RuntimeException("system server dead?", e); + } } /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 1a7547d..3a9b26a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -498,7 +498,15 @@ public class InputMethodService extends AbstractInputMethodService { if (showingCandidates) { setCandidatesViewShown(true); } - showWindow(showingInput); + if (showingInput) { + // If we are showing the full soft keyboard, then go through + // this path to take care of current decisions about fullscreen + // etc. + onShowRequested(InputMethod.SHOW_EXPLICIT); + } else { + // Otherwise just put it back for its candidates. + showWindow(false); + } } } @@ -649,16 +657,14 @@ public class InputMethodService extends AbstractInputMethodService { /** * Override this to control when the input method should run in * fullscreen mode. The default implementation runs in fullsceen only - * when the screen is in landscape mode and the input view is being - * shown ({@link #onEvaluateInputViewShown} returns true). If you change what + * when the screen is in landscape mode. If you change what * this returns, you will need to call {@link #updateFullscreenMode()} * yourself whenever the returned value may have changed to have it * re-evaluated and applied. */ public boolean onEvaluateFullscreenMode() { Configuration config = getResources().getConfiguration(); - return config.orientation == Configuration.ORIENTATION_LANDSCAPE - && onEvaluateInputViewShown(); + return config.orientation == Configuration.ORIENTATION_LANDSCAPE; } /** @@ -870,7 +876,11 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Called when an input session is starting or restarting. + * Called when the input view is being shown and input has started on + * a new editor. This will always be called after {@link #onStartInput}, + * allowing you to do your general setup there and just view-specific + * setup here. You are guaranteed that {@link #onCreateInputView()} will + * have been called some time before this function is called. * * @param info Description of the type of text being edited. * @param restarting Set to true if we are restarting input on the @@ -892,6 +902,9 @@ public class InputMethodService extends AbstractInputMethodService { * as per {@link InputMethod#showSoftInput(int) InputMethod.showSoftInput(int)}. */ public void onShowRequested(int flags) { + if (!onEvaluateInputViewShown()) { + return; + } if ((flags&InputMethod.SHOW_EXPLICIT) == 0 && onEvaluateFullscreenMode()) { // Don't show if this is not explicit requested by the user and // the input method is fullscreen. That would be too disruptive. @@ -911,9 +924,11 @@ public class InputMethodService extends AbstractInputMethodService { boolean wasVisible = mWindowVisible; mWindowVisible = true; if (!mShowInputRequested) { - if (showInput) { - doShowInput = true; - mShowInputRequested = true; + if (mInputStarted) { + if (showInput) { + doShowInput = true; + mShowInputRequested = true; + } } } else { showInput = true; @@ -1001,7 +1016,7 @@ public class InputMethodService extends AbstractInputMethodService { mInputEditorInfo = attribute; onStartInput(attribute, restarting); if (mWindowVisible) { - if (mWindowCreated) { + if (mShowInputRequested) { onStartInputView(mInputEditorInfo, restarting); } startExtractingText(); diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 6f044b6..2f3b54b 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -153,9 +153,10 @@ public class KeyboardView extends View implements View.OnClickListener { /** Listener for {@link OnKeyboardActionListener}. */ private OnKeyboardActionListener mKeyboardActionListener; - private static final int MSG_REMOVE_PREVIEW = 1; - private static final int MSG_REPEAT = 2; - private static final int MSG_LONGPRESS = 3; + private static final int MSG_SHOW_PREVIEW = 1; + private static final int MSG_REMOVE_PREVIEW = 2; + private static final int MSG_REPEAT = 3; + private static final int MSG_LONGPRESS = 4; private int mVerticalCorrection; private int mProximityThreshold; @@ -198,7 +199,8 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int REPEAT_INTERVAL = 50; // ~20 keys per second private static final int REPEAT_START_DELAY = 400; - private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); + private static final int LONGPRESS_TIMEOUT = 800; + // Deemed to be too short : ViewConfiguration.getLongPressTimeout(); private static int MAX_NEARBY_KEYS = 12; private int[] mDistances = new int[MAX_NEARBY_KEYS]; @@ -215,6 +217,9 @@ public class KeyboardView extends View implements View.OnClickListener { @Override public void handleMessage(Message msg) { switch (msg.what) { + case MSG_SHOW_PREVIEW: + mPreviewText.setVisibility(VISIBLE); + break; case MSG_REMOVE_PREVIEW: mPreviewText.setVisibility(INVISIBLE); break; @@ -731,7 +736,7 @@ public class KeyboardView extends View implements View.OnClickListener { return adjustCase(key.label); } } - + private void showPreview(int keyIndex) { int oldKeyIndex = mCurrentKeyIndex; final PopupWindow previewPopup = mPreviewPopup; @@ -751,6 +756,7 @@ public class KeyboardView extends View implements View.OnClickListener { } // If key changed and preview is on ... if (oldKeyIndex != mCurrentKeyIndex && mShowPreview) { + mHandler.removeMessages(MSG_SHOW_PREVIEW); if (previewPopup.isShowing()) { if (keyIndex == NOT_A_KEY) { mHandler.sendMessageDelayed(mHandler @@ -803,7 +809,7 @@ public class KeyboardView extends View implements View.OnClickListener { mPreviewText.getBackground().setState( key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET); if (previewPopup.isShowing()) { - previewPopup.update(mPopupPreviewX + mOffsetInWindow[0], + previewPopup.update(mPopupParent, mPopupPreviewX + mOffsetInWindow[0], mPopupPreviewY + mOffsetInWindow[1], popupWidth, popupHeight); } else { @@ -811,7 +817,8 @@ public class KeyboardView extends View implements View.OnClickListener { mPopupPreviewX + mOffsetInWindow[0], mPopupPreviewY + mOffsetInWindow[1]); } - mPreviewText.setVisibility(VISIBLE); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_PREVIEW, keyIndex, 0), + ViewConfiguration.getTapTimeout()); } } } @@ -1014,6 +1021,7 @@ public class KeyboardView extends View implements View.OnClickListener { break; case MotionEvent.ACTION_UP: + mHandler.removeMessages(MSG_SHOW_PREVIEW); mHandler.removeMessages(MSG_REPEAT); mHandler.removeMessages(MSG_LONGPRESS); if (keyIndex == mCurrentKey) { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index e065063..ed7c366 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -237,7 +237,7 @@ public abstract class BatteryStats { public abstract long computeBatteryRealtime(long curTime, int which); /** - * Returns the total, last, or current uptime in micropeconds. + * Returns the total, last, or current uptime in microseconds. * * @param curTime the current elapsed realtime in microseconds. * @param which one of STATS_TOTAL, STATS_LAST, or STATS_CURRENT. @@ -299,6 +299,9 @@ public abstract class BatteryStats { } private final String formatRatioLocked(long num, long den) { + if (den == 0L) { + return "---%"; + } float perc = ((float)num) / ((float)den) * 100; mFormatBuilder.setLength(0); mFormatter.format("%.1f%%", perc); @@ -352,18 +355,20 @@ public abstract class BatteryStats { long batteryUptime = computeBatteryUptime(uSecNow, which); long batteryRealtime = computeBatteryRealtime(getBatteryRealtime(uSecTime), which); long elapsedRealtime = computeRealtime(uSecTime, which); + long uptime = computeUptime(SystemClock.uptimeMillis() * 1000, which); + pw.println(prefix - + " On battery: " + formatTimeMs(batteryUptime) + "(" + + " On battery: " + formatTimeMs(batteryUptime / 1000) + "(" + formatRatioLocked(batteryUptime, batteryRealtime) + ") uptime, " - + formatTimeMs(batteryRealtime) + "(" + + formatTimeMs(batteryRealtime / 1000) + "(" + formatRatioLocked(batteryRealtime, elapsedRealtime) + ") realtime"); pw.println(prefix + " Total: " - + formatTimeMs(computeUptime(SystemClock.uptimeMillis() * 1000, which)) + + formatTimeMs(uptime / 1000) + "uptime, " - + formatTimeMs(elapsedRealtime) + + formatTimeMs(elapsedRealtime / 1000) + "realtime"); pw.println(" "); @@ -479,7 +484,7 @@ public abstract class BatteryStats { if (startTime != 0 || starts != 0 || launches != 0) { pw.println(prefix + " Service " + sent.getKey() + ":"); pw.println(prefix + " Time spent started: " - + formatTimeMs(startTime)); + + formatTimeMs(startTime / 1000)); pw.println(prefix + " Starts: " + starts + ", launches: " + launches); apkActivity = true; diff --git a/core/java/android/provider/Gmail.java b/core/java/android/provider/Gmail.java index 1bbcc33..325f19d 100644 --- a/core/java/android/provider/Gmail.java +++ b/core/java/android/provider/Gmail.java @@ -554,13 +554,15 @@ public final class Gmail { * * @param account the account of the conversation * @param conversationId the conversation - * @param maxMessageId the highest message id to whose labels should be changed + * @param maxServerMessageId the highest message id to whose labels should be changed. Note that + * everywhere else in this file messageId means local message id but here you need to use a + * server message id. * @param label the label to add or remove * @param add true to add the label, false to remove it * @throws NonexistentLabelException thrown if the label does not exist */ public void addOrRemoveLabelOnConversation( - String account, long conversationId, long maxMessageId, String label, + String account, long conversationId, long maxServerMessageId, String label, boolean add) throws NonexistentLabelException { if (TextUtils.isEmpty(account)) { @@ -571,7 +573,7 @@ public final class Gmail { AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels"); ContentValues values = new ContentValues(); values.put(LabelColumns.CANONICAL_NAME, label); - values.put(ConversationColumns.MAX_MESSAGE_ID, maxMessageId); + values.put(ConversationColumns.MAX_MESSAGE_ID, maxServerMessageId); mContentResolver.insert(uri, values); } else { String encodedLabel; @@ -584,7 +586,7 @@ public final class Gmail { AUTHORITY_PLUS_CONVERSATIONS + account + "/" + conversationId + "/labels/" + encodedLabel); mContentResolver.delete( - uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxMessageId}); + uri, ConversationColumns.MAX_MESSAGE_ID, new String[]{"" + maxServerMessageId}); } } @@ -2373,7 +2375,7 @@ public final class Gmail { /** * @return the max message id in the conversation */ - public long getMaxMessageId() { + public long getMaxServerMessageId() { return mCursor.getLong(mMaxMessageIdIndex); } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index 0184db8..87a02e6 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -148,6 +148,13 @@ public final class MediaStore public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; /** + * The name of the Intent-extra used to indicate a Uri to be used to + * store the requested image or video. + * @hide + */ + public final static String EXTRA_OUTPUT = "output"; + + /** * Common fields for most MediaProvider tables */ diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a18f37c..abbfd5b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2121,6 +2121,12 @@ public final class Settings { = "youtube_use_proxy"; /** + * MMS - maximum message size in bytes for a MMS message. + */ + public static final String MMS_MAXIMUM_MESSAGE_SIZE + = "mms_maximum_message_size"; + + /** * Event tags from the kernel event log to upload during checkin. */ public static final String CHECKIN_EVENTS = "checkin_events"; @@ -2685,6 +2691,22 @@ public final class Settings { "short_keylight_delay_ms"; /** + * URL that points to the voice search servers. To be factored out of this class. + */ + public static final String VOICE_SEARCH_URL = "voice_search_url"; + + /** + * Speech encoding used with voice search on 3G networks. To be factored out of this class. + */ + public static final String VOICE_SEARCH_ENCODING_THREE_G = "voice_search_encoding_three_g"; + + /** + * Speech encoding used with voice search on WIFI networks. To be factored out of this class. + */ + public static final String VOICE_SEARCH_ENCODING_WIFI = "voice_search_encoding_wifi"; + + + /** * @deprecated * @hide */ diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index be784ff..8486e4b 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -84,8 +84,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION); mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION); - mIntentFilter.addAction(BluetoothIntent.BONDING_CREATED_ACTION); - mIntentFilter.addAction(BluetoothIntent.BONDING_REMOVED_ACTION); + mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION); mContext.registerReceiver(mReceiver, mIntentFilter); if (device.isEnabled()) { @@ -111,10 +110,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { onBluetoothEnable(); } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) { onBluetoothDisable(); - } else if (action.equals(BluetoothIntent.BONDING_CREATED_ACTION)) { - setSinkPriority(address, BluetoothA2dp.PRIORITY_AUTO); - } else if (action.equals(BluetoothIntent.BONDING_REMOVED_ACTION)) { - setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF); + } else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) { + int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE, + BluetoothError.ERROR); + switch(bondState) { + case BluetoothDevice.BOND_BONDED: + setSinkPriority(address, BluetoothA2dp.PRIORITY_AUTO); + break; + case BluetoothDevice.BOND_BONDING: + case BluetoothDevice.BOND_NOT_BONDED: + setSinkPriority(address, BluetoothA2dp.PRIORITY_OFF); + break; + } } } }; diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 9bdab9f..3ce34c3 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -26,6 +26,7 @@ package android.server; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; // just for dump() +import android.bluetooth.BluetoothError; import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDevice; import android.bluetooth.IBluetoothDeviceCallback; @@ -49,14 +50,19 @@ import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Map; public class BluetoothDeviceService extends IBluetoothDevice.Stub { private static final String TAG = "BluetoothDeviceService"; + private static final boolean DBG = true; + private int mNativeData; private BluetoothEventLoop mEventLoop; private IntentFilter mIntentFilter; private boolean mIsAirplaneSensitive; + private final BondState mBondState = new BondState(); // local cache of bondings private volatile boolean mIsEnabled; // local cache of isEnabledNative() private boolean mIsDiscovering; @@ -79,10 +85,13 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized void init() { initializeNativeDataNative(); mIsEnabled = (isEnabledNative() == 1); + if (mIsEnabled) { + mBondState.loadBondState(); + } mIsDiscovering = false; mEventLoop = new BluetoothEventLoop(mContext, this); registerForAirplaneMode(); - + disableEsco(); // TODO: enable eSCO support once its fully supported } private native void initializeNativeDataNative(); @@ -112,7 +121,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public synchronized boolean disable() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); - + if (mEnableThread != null && mEnableThread.isAlive()) { return false; } @@ -171,7 +180,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { }; private EnableThread mEnableThread; - private String mOutgoingBondingDevAddress = null; private class EnableThread extends Thread { private final IBluetoothDeviceCallback mEnableCallback; @@ -198,6 +206,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { Settings.Secure.BLUETOOTH_ON, 1); mIsDiscovering = false; Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION); + mBondState.loadBondState(); mContext.sendBroadcast(intent, BLUETOOTH_PERM); mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000); } @@ -208,6 +217,119 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { private native int enableNative(); private native int disableNative(); + /* package */ BondState getBondState() { + return mBondState; + } + + /** local cache of bonding state. + /* we keep our own state to track the intermediate state BONDING, which + /* bluez does not track. + * All addreses must be passed in upper case. + */ + public class BondState { + private final HashMap<String, Integer> mState = new HashMap<String, Integer>(); + private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>(); + + public synchronized void loadBondState() { + if (!mIsEnabled) { + return; + } + String[] bonds = listBondingsNative(); + if (bonds == null) { + return; + } + mState.clear(); + if (DBG) log("found " + bonds.length + " bonded devices"); + for (String address : bonds) { + mState.put(address.toUpperCase(), BluetoothDevice.BOND_BONDED); + } + } + + public synchronized void setBondState(String address, int state) { + setBondState(address, state, 0); + } + + /** reason is ignored unless state == BOND_NOT_BONDED */ + public synchronized void setBondState(String address, int state, int reason) { + int oldState = getBondState(address); + if (oldState == state) { + return; + } + if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" + + reason + ")"); + Intent intent = new Intent(BluetoothIntent.BOND_STATE_CHANGED_ACTION); + intent.putExtra(BluetoothIntent.ADDRESS, address); + intent.putExtra(BluetoothIntent.BOND_STATE, state); + intent.putExtra(BluetoothIntent.BOND_PREVIOUS_STATE, oldState); + if (state == BluetoothDevice.BOND_NOT_BONDED) { + if (reason <= 0) { + Log.w(TAG, "setBondState() called to unbond device with invalid reason code " + + "Setting reason = BOND_RESULT_REMOVED"); + reason = BluetoothDevice.UNBOND_REASON_REMOVED; + } + intent.putExtra(BluetoothIntent.REASON, reason); + mState.remove(address); + } else { + mState.put(address, state); + } + if (state == BluetoothDevice.BOND_BONDING) { + mPinAttempt.put(address, Integer.valueOf(0)); + } else { + mPinAttempt.remove(address); + } + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + + public synchronized int getBondState(String address) { + Integer state = mState.get(address); + if (state == null) { + return BluetoothDevice.BOND_NOT_BONDED; + } + return state.intValue(); + } + + public synchronized String[] listBonds() { + ArrayList<String> result = new ArrayList<String>(mState.size()); + for (Map.Entry<String, Integer> e : mState.entrySet()) { + if (e.getValue().intValue() == BluetoothDevice.BOND_BONDED) { + result.add(e.getKey()); + } + } + return result.toArray(new String[result.size()]); + } + + public synchronized int getAttempt(String address) { + Integer attempt = mPinAttempt.get(address); + if (attempt == null) { + return 0; + } + return attempt.intValue(); + } + + public synchronized void attempt(String address) { + Integer attempt = mPinAttempt.get(address); + if (attempt == null) { + return; + } + mPinAttempt.put(address, new Integer(attempt.intValue() + 1)); + } + + } + private native String[] listBondingsNative(); + + private static String toBondStateString(int bondState) { + switch (bondState) { + case BluetoothDevice.BOND_NOT_BONDED: + return "not bonded"; + case BluetoothDevice.BOND_BONDING: + return "bonding"; + case BluetoothDevice.BOND_BONDED: + return "bonded"; + default: + return "??????"; + } + } + public synchronized String getAddress() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getAddressNative(); @@ -231,12 +353,6 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } private native boolean setNameNative(String name); - public synchronized String[] listBondings() { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return listBondingsNative(); - } - private native String[] listBondingsNative(); - public synchronized String getMajorClass() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getMajorClassNative(); @@ -551,106 +667,45 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } private native boolean disconnectRemoteDeviceNative(String address); - private static final int MAX_OUTSTANDING_ASYNC = 32; - /** - * This method initiates a Bonding request to a remote device. - * - * - * @param address The Bluetooth address of the remote device - * - * @see #createBonding - * @see #cancelBondingProcess - * @see #removeBonding - * @see #hasBonding - * @see #listBondings - * - * @see android.bluetooth.PasskeyAgent - */ - public synchronized boolean createBonding(String address, IBluetoothDeviceCallback callback) { + public synchronized boolean createBond(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!BluetoothDevice.checkBluetoothAddress(address)) { return false; } - - HashMap<String, IBluetoothDeviceCallback> callbacks = mEventLoop.getCreateBondingCallbacks(); - if (callbacks.containsKey(address)) { - Log.w(TAG, "createBonding() already in progress for " + address); + address = address.toUpperCase(); + if (mBondState.getBondState(address) != BluetoothDevice.BOND_NOT_BONDED) { return false; } - // Protect from malicious clients - limit number of outstanding requests - if (callbacks.size() > MAX_OUTSTANDING_ASYNC) { - Log.w(TAG, "Too many outstanding bonding requests, dropping request for " + address); - return false; - } - - callbacks.put(address, callback); if (!createBondingNative(address, 60000 /* 1 minute */)) { - callbacks.remove(address); return false; } - mOutgoingBondingDevAddress = address; + + mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); return true; } - private native boolean createBondingNative(String address, int timeout_ms); - - /*package*/ String getOutgoingBondingDevAddress() { - return mOutgoingBondingDevAddress; - } - /*package*/ void setOutgoingBondingDevAddress(String outgoingBondingDevAddress) { - mOutgoingBondingDevAddress = outgoingBondingDevAddress; - } - - /** - * This method cancels a pending bonding request. - * - * @param address The Bluetooth address of the remote device to which a - * bonding request has been initiated. - * - * Note: When a request is canceled, method - * {@link CreateBondingResultNotifier#notifyAuthenticationFailed} - * will be called on the object passed to method - * {@link #createBonding}. - * - * Note: it is safe to call this method when there is no outstanding - * bonding request. - * - * @see #createBonding - * @see #cancelBondingProcess - * @see #removeBonding - * @see #hasBonding - * @see #listBondings - */ - public synchronized boolean cancelBondingProcess(String address) { + public synchronized boolean cancelBondProcess(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!BluetoothDevice.checkBluetoothAddress(address)) { return false; } - return cancelBondingProcessNative(address); + address = address.toUpperCase(); + if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) { + return false; + } + + mBondState.setBondState(address, BluetoothDevice.BOND_NOT_BONDED, + BluetoothDevice.UNBOND_REASON_CANCELLED); + cancelBondingProcessNative(address); + return true; } private native boolean cancelBondingProcessNative(String address); - /** - * This method removes a bonding to a remote device. This is a local - * operation only, resulting in this adapter "forgetting" the bonding - * information about the specified remote device. The other device itself - * does not know what the bonding has been torn down. The next time either - * device attemps to connect to the other, the connection will fail, and - * the pairing procedure will have to be re-initiated. - * - * @param address The Bluetooth address of the remote device. - * - * @see #createBonding - * @see #cancelBondingProcess - * @see #removeBonding - * @see #hasBonding - * @see #listBondings - */ - public synchronized boolean removeBonding(String address) { + public synchronized boolean removeBond(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!BluetoothDevice.checkBluetoothAddress(address)) { @@ -660,14 +715,18 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } private native boolean removeBondingNative(String address); - public synchronized boolean hasBonding(String address) { + public synchronized String[] listBonds() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + return mBondState.listBonds(); + } + + public synchronized int getBondState(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (!BluetoothDevice.checkBluetoothAddress(address)) { - return false; + return BluetoothError.ERROR; } - return hasBondingNative(address); + return mBondState.getBondState(address.toUpperCase()); } - private native boolean hasBondingNative(String address); public synchronized String[] listAclConnections() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); @@ -1016,6 +1075,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } private native byte[] getRemoteServiceRecordNative(String address, int handle); + private static final int MAX_OUTSTANDING_ASYNC = 32; + // AIDL does not yet support short's public synchronized boolean getRemoteServiceChannel(String address, int uuid16, IBluetoothDeviceCallback callback) { @@ -1051,6 +1112,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { !BluetoothDevice.checkBluetoothAddress(address)) { return false; } + address = address.toUpperCase(); Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); if (data == null) { Log.w(TAG, "setPin(" + address + ") called but no native data available, " + @@ -1076,6 +1138,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { if (!BluetoothDevice.checkBluetoothAddress(address)) { return false; } + address = address.toUpperCase(); Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); if (data == null) { Log.w(TAG, "cancelPin(" + address + ") called but no native data available, " + @@ -1143,25 +1206,20 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { BluetoothHeadset headset = new BluetoothHeadset(mContext, null); - pw.println("\n--Bondings--"); - String[] addresses = listBondings(); - for (String address : addresses) { - String name = getRemoteName(address); - pw.println(address + " (" + name + ")"); - } + String[] addresses = listRemoteDevices(); - pw.println("\n--Current ACL Connections--"); - addresses = listAclConnections(); + pw.println("\n--Known devices--"); for (String address : addresses) { - String name = getRemoteName(address); - pw.println(address + " (" + name + ")"); + pw.printf("%s %10s (%d) %s\n", address, + toBondStateString(mBondState.getBondState(address)), + mBondState.getAttempt(address), + getRemoteName(address)); } - pw.println("\n--Known Devices--"); - addresses = listRemoteDevices(); + addresses = listAclConnections(); + pw.println("\n--ACL connected devices--"); for (String address : addresses) { - String name = getRemoteName(address); - pw.println(address + " (" + name + ")"); + pw.println(address); } // Rather not do this from here, but no-where else and I need this @@ -1189,4 +1247,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive); } + + private static void log(String msg) { + Log.d(TAG, msg); + } } diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 2d8aacc..4f63f98 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -16,8 +16,9 @@ package android.server; -import android.bluetooth.BluetoothClass.Device; +import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothError; import android.bluetooth.BluetoothIntent; import android.bluetooth.IBluetoothDeviceCallback; import android.content.Context; @@ -41,11 +42,9 @@ class BluetoothEventLoop { private int mNativeData; private Thread mThread; private boolean mInterrupted; - private HashMap<String, IBluetoothDeviceCallback> mCreateBondingCallbacks; private HashMap<String, Integer> mPasskeyAgentRequestData; private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; - private HashMap<String, Boolean> mDefaultPinData; - private BluetoothDeviceService mBluetoothService; + private BluetoothDeviceService mBluetoothService; private Context mContext; private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; @@ -57,10 +56,8 @@ class BluetoothEventLoop { /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) { mBluetoothService = bluetoothService; mContext = context; - mCreateBondingCallbacks = new HashMap(); mPasskeyAgentRequestData = new HashMap(); mGetRemoteServiceChannelCallbacks = new HashMap(); - mDefaultPinData = new HashMap(); initializeNativeDataNative(); } private native void initializeNativeDataNative(); @@ -74,9 +71,6 @@ class BluetoothEventLoop { } private native void cleanupNativeDataNative(); - /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getCreateBondingCallbacks() { - return mCreateBondingCallbacks; - } /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() { return mGetRemoteServiceChannelCallbacks; } @@ -235,33 +229,24 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_PERM); } - private void onCreateBondingResult(String address, boolean result) { - mBluetoothService.setOutgoingBondingDevAddress(null); - IBluetoothDeviceCallback callback = mCreateBondingCallbacks.get(address); - if (callback != null) { - try { - callback.onCreateBondingResult(address, - result ? BluetoothDevice.RESULT_SUCCESS : - BluetoothDevice.RESULT_FAILURE); - } catch (RemoteException e) {} - mCreateBondingCallbacks.remove(address); - } + private void onCreateBondingResult(String address, int result) { + address = address.toUpperCase(); + if (result == BluetoothError.SUCCESS) { + mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); + } else { + mBluetoothService.getBondState().setBondState(address, + BluetoothDevice.BOND_NOT_BONDED, result); + } } - + public void onBondingCreated(String address) { - Intent intent = new Intent(BluetoothIntent.BONDING_CREATED_ACTION); - intent.putExtra(BluetoothIntent.ADDRESS, address); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); + mBluetoothService.getBondState().setBondState(address.toUpperCase(), + BluetoothDevice.BOND_BONDED); } - - public void onBondingRemoved(String address) { - Intent intent = new Intent(BluetoothIntent.BONDING_REMOVED_ACTION); - intent.putExtra(BluetoothIntent.ADDRESS, address); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - if (mDefaultPinData.containsKey(address)) { - mDefaultPinData.remove(address); - } + public void onBondingRemoved(String address) { + mBluetoothService.getBondState().setBondState(address.toUpperCase(), + BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED); } public void onNameChanged(String name) { @@ -271,24 +256,24 @@ class BluetoothEventLoop { } public void onPasskeyAgentRequest(String address, int nativeData) { - mPasskeyAgentRequestData.put(address, new Integer(nativeData)); - - if (address.equals(mBluetoothService.getOutgoingBondingDevAddress())) { + address = address.toUpperCase(); + mPasskeyAgentRequestData.put(address, new Integer(nativeData)); + + if (mBluetoothService.getBondState().getBondState(address) == + BluetoothDevice.BOND_BONDING) { + // we initiated the bonding int btClass = mBluetoothService.getRemoteClass(address); - int remoteDeviceClass = Device.getDevice(btClass); - if (remoteDeviceClass == Device.AUDIO_VIDEO_WEARABLE_HEADSET || - remoteDeviceClass == Device.AUDIO_VIDEO_HANDSFREE || - remoteDeviceClass == Device.AUDIO_VIDEO_HEADPHONES || - remoteDeviceClass == Device.AUDIO_VIDEO_PORTABLE_AUDIO || - remoteDeviceClass == Device.AUDIO_VIDEO_CAR_AUDIO || - remoteDeviceClass == Device.AUDIO_VIDEO_HIFI_AUDIO) { - if (!mDefaultPinData.containsKey(address)) { - mDefaultPinData.put(address, false); - } - if (!mDefaultPinData.get(address)) { - mDefaultPinData.remove(address); - mDefaultPinData.put(address, true); + // try 0000 once if the device looks dumb + switch (BluetoothClass.Device.getDevice(btClass)) { + case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: + case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: + case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: + case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: + case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: + if (mBluetoothService.getBondState().getAttempt(address) < 1) { + mBluetoothService.getBondState().attempt(address); mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); return; } @@ -298,18 +283,17 @@ class BluetoothEventLoop { intent.putExtra(BluetoothIntent.ADDRESS, address); mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); } - + public void onPasskeyAgentCancel(String address) { + address = address.toUpperCase(); mPasskeyAgentRequestData.remove(address); - if (mDefaultPinData.containsKey(address)) { - mDefaultPinData.remove(address); - mDefaultPinData.put(address, false); - } Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); intent.putExtra(BluetoothIntent.ADDRESS, address); mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED, + BluetoothDevice.UNBOND_REASON_CANCELLED); } - + private void onGetRemoteServiceChannelResult(String address, int channel) { IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address); if (callback != null) { diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index ac4cdb9..c8f395e 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -704,6 +704,7 @@ public final class SearchableInfo implements Parcelable { * positioned at the location in the buffer where it was written. */ public SearchableInfo(Parcel in) { + mSearchable = in.readInt() != 0; mLabelId = in.readInt(); mSearchActivity = ComponentName.readFromParcel(in); mHintId = in.readInt(); @@ -733,6 +734,7 @@ public final class SearchableInfo implements Parcelable { } public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mSearchable ? 1 : 0); dest.writeInt(mLabelId); mSearchActivity.writeToParcel(dest, flags); dest.writeInt(mHintId); diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 64356d5..405d934 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -659,8 +659,8 @@ public class TextUtils { p.writeInt(0); } - p.writeInt(tas.getTextSize()); p.writeInt(tas.getTextStyle()); + p.writeInt(tas.getTextSize()); ColorStateList csl = tas.getTextColor(); if (csl == null) { @@ -799,8 +799,8 @@ public class TextUtils { p.readInt() != 0 ? p.readString() : null, - p.readInt(), - p.readInt(), + p.readInt(), // style + p.readInt(), // size p.readInt() != 0 ? ColorStateList.CREATOR.createFromParcel(p) : null, diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index a6def51..6af4915 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -21,6 +21,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.IBinder; import android.os.LocalPowerManager; +import android.view.animation.Animation; /** * This interface supplies all UI-specific behavior of the window manager. An @@ -247,6 +248,13 @@ public interface WindowManagerPolicy { public WindowState getAppStartingWindow(); /** + * Is this window visible? It is not visible if there is no + * surface, or we are in the process of running an exit animation + * that will remove the surface. + */ + boolean isVisibleLw(); + + /** * Is this window currently visible to the user on-screen? It is * displayed either if it is visible or it is currently running an * animation before no longer being visible. Must be called with the @@ -282,7 +290,7 @@ public interface WindowManagerPolicy { * regardless of whether the client or window manager would like * it shown. Must be called with the window manager lock held. */ - public void hideLw(); + public void hideLw(boolean doAnimation); /** * Can be called to undo the effect of {@link #hideLw}, allowing a @@ -290,7 +298,7 @@ public interface WindowManagerPolicy { * also like it to be shown. Must be called with the window manager * lock held. */ - public void showLw(); + public void showLw(boolean doAnimation); } /** No transition happening. */ diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 27461ff..bd7b050 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -177,6 +177,19 @@ public interface InputConnection { public boolean commitCompletion(CompletionInfo text); /** + * Tell the editor that you are starting a batch of editor operations. + * The editor will try to avoid sending you updates about its state + * until {@link #endBatchEdit} is called. + */ + public boolean beginBatchEdit(); + + /** + * Tell the editor that you are done with a batch edit previously + * initiated with {@link #endBatchEdit}. + */ + public boolean endBatchEdit(); + + /** * Send a key event to the process that is currently attached through * this input connection. The event will be dispatched like a normal * key event, to the currently focused; this generally is the view that diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index a41955c..f65b2a1 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -77,6 +77,14 @@ public class InputConnectionWrapper implements InputConnection { return mBase.commitCompletion(text); } + public boolean beginBatchEdit() { + return mBase.beginBatchEdit(); + } + + public boolean endBatchEdit() { + return mBase.endBatchEdit(); + } + public boolean sendKeyEvent(KeyEvent event) { return mBase.sendKeyEvent(event); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index ba40782..a9a9594 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -293,6 +293,14 @@ public final class InputMethodManager { return false; } + public boolean beginBatchEdit() { + return false; + } + + public boolean endBatchEdit() { + return false; + } + public boolean commitCompletion(CompletionInfo text) { return false; } @@ -401,6 +409,12 @@ public final class InputMethodManager { }; final InputConnection mDummyInputConnection = new BaseInputConnection(this) { + public boolean beginBatchEdit() { + return false; + } + public boolean endBatchEdit() { + return false; + } public boolean commitText(CharSequence text, int newCursorPosition) { return false; } diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index c45ab29..3f2bbe5 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -167,7 +167,7 @@ class LoadListener extends Handler implements EventHandler { * should do with them. */ if (mNativeLoader != 0) { - commitHeaders(); + commitHeadersCheckRedirect(); } break; @@ -458,11 +458,6 @@ class LoadListener extends Handler implements EventHandler { if (Config.LOGV) { Log.v(LOGTAG, "LoadListener.data(): url: " + url()); } - - if (ignoreCallbacks()) { - return; - } - // Decode base64 data // Note: It's fine that we only decode base64 here and not in the other // data call because the only caller of the stream version is not @@ -484,7 +479,7 @@ class LoadListener extends Handler implements EventHandler { sendMessage = mDataBuilder.isEmpty(); mDataBuilder.append(data, 0, length); } - if (sendMessage) { + if (sendMessage && !ignoreCallbacks()) { // Send a message whenever data comes in after a write to WebCore sendMessageInternal(obtainMessage(MSG_CONTENT_DATA)); } @@ -850,7 +845,8 @@ class LoadListener extends Handler implements EventHandler { return mContentLength; } - private void commitHeaders() { + // Commit the headers if the status code is not a redirect. + private void commitHeadersCheckRedirect() { if (mCancelled) return; // do not call webcore if it is redirect. According to the code in @@ -860,6 +856,11 @@ class LoadListener extends Handler implements EventHandler { return; } + commitHeaders(); + } + + // This commits the headers without checking the response status code. + private void commitHeaders() { // Commit the headers to WebCore int nativeResponse = createNativeResponse(); // The native code deletes the native response object. @@ -1075,7 +1076,11 @@ class LoadListener extends Handler implements EventHandler { } } } else { - cancel(); + // With a null redirect, commit the original headers, the buffered + // data, and then finish the load. + commitHeaders(); + commitLoad(); + nativeFinished(); } if (Config.LOGV) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 9cfc622..cab278be 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -288,10 +288,6 @@ public class WebView extends AbsoluteLayout // The time that the Zoom Controls are visible before fading away private static final long ZOOM_CONTROLS_TIMEOUT = ViewConfiguration.getZoomControlsTimeout(); - // Wait a short time before sending kit focus message, in case - // the user is still moving around, to avoid rebuilding the display list - // prematurely - private static final long SET_KIT_FOCUS_DELAY = 250; // The amount of content to overlap between two screens when going through // pages with the space bar, in pixels. private static final int PAGE_SCROLL_OVERLAP = 24; @@ -4725,8 +4721,7 @@ public class WebView extends AbsoluteLayout // called by JNI private void sendKitFocus() { WebViewCore.FocusData focusData = new WebViewCore.FocusData(mFocusData); - mWebViewCore.sendMessageDelayed(EventHub.SET_KIT_FOCUS, focusData, - SET_KIT_FOCUS_DELAY); + mWebViewCore.sendMessage(EventHub.SET_KIT_FOCUS, focusData); } // called by JNI diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java index f2868af..5e692d4 100644 --- a/core/java/android/widget/Button.java +++ b/core/java/android/widget/Button.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.KeyEvent; +import android.widget.RemoteViews.RemoteView; /** @@ -54,6 +55,7 @@ import android.view.KeyEvent; * {@link android.R.styleable#View View Attributes} * </p> */ +@RemoteView public class Button extends TextView { public Button(Context context) { this(context, null); diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java index 5c56428..4c1cbf6 100644 --- a/core/java/android/widget/ImageButton.java +++ b/core/java/android/widget/ImageButton.java @@ -21,6 +21,7 @@ import android.os.Handler; import android.os.Message; import android.util.AttributeSet; import android.view.MotionEvent; +import android.widget.RemoteViews.RemoteView; import java.util.Map; @@ -36,6 +37,7 @@ import java.util.Map; * {@link android.R.styleable#View View Attributes} * </p> */ +@RemoteView public class ImageButton extends ImageView { public ImageButton(Context context) { this(context, null); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 54951b7..5721095 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -16,6 +16,8 @@ package android.widget; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; @@ -31,6 +33,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.LayoutInflater.Filter; +import android.view.View.OnClickListener; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -371,6 +374,52 @@ public class RemoteViews implements Parcelable, Filter { public final static int TAG = 6; } + + /** + * Equivalent to calling + * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} + * to launch the provided {@link PendingIntent}. + */ + private class SetOnClickPendingIntent extends Action { + public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { + this.viewId = id; + this.pendingIntent = pendingIntent; + } + + public SetOnClickPendingIntent(Parcel parcel) { + viewId = parcel.readInt(); + pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(TAG); + dest.writeInt(viewId); + pendingIntent.writeToParcel(dest, 0 /* no flags */); + } + + @Override + public void apply(View root) { + final View target = root.findViewById(viewId); + if (target != null && pendingIntent != null) { + OnClickListener listener = new OnClickListener() { + public void onClick(View v) { + try { + // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? + pendingIntent.send(); + } catch (CanceledException e) { + throw new ActionException(e.toString()); + } + } + }; + target.setOnClickListener(listener); + } + } + + int viewId; + PendingIntent pendingIntent; + + public final static int TAG = 7; + } /** * Create a new RemoteViews object that will display the views contained @@ -419,6 +468,9 @@ public class RemoteViews implements Parcelable, Filter { case SetProgressBar.TAG: mActions.add(new SetProgressBar(parcel)); break; + case SetOnClickPendingIntent.TAG: + mActions.add(new SetOnClickPendingIntent(parcel)); + break; default: throw new ActionException("Tag " + tag + "not found"); } @@ -491,8 +543,6 @@ public class RemoteViews implements Parcelable, Filter { * * @param viewId The id of the view whose drawable should change * @param bitmap The new Bitmap for the drawable - * - * @hide pending API Council approval to extend the public API */ public void setImageViewBitmap(int viewId, Bitmap bitmap) { addAction(new SetImageViewBitmap(viewId, bitmap)); @@ -533,6 +583,18 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling + * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} + * to launch the provided {@link PendingIntent}. + * + * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked + * @param pendingIntent The {@link PendingIntent} to send when user clicks + */ + public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { + addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); + } + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 8baed7d..aa70663 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -235,6 +235,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener float[] mTmpOffset = new float[2]; ExtractedTextRequest mExtracting; final ExtractedText mTmpExtracted = new ExtractedText(); + boolean mBatchEditing; } InputMethodState mInputMethodState; @@ -3505,7 +3506,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ InputMethodManager imm = InputMethodManager.peekInstance(); - if (highlight != null && mInputMethodState != null && imm != null) { + if (highlight != null && mInputMethodState != null + && !mInputMethodState.mBatchEditing && imm != null) { if (imm.isActive(this)) { int candStart = -1; int candEnd = -1; @@ -3865,6 +3867,38 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Called by the framework in response to a request to begin a batch + * of edit operations from the current input method, as a result of + * it calling {@link InputConnection#beginBatchEdit + * InputConnection.beginBatchEdit()}. The default implementation sets + * up the TextView's internal state to take care of this; if overriding + * you should call through to the super class. + */ + public void onBeginBatchEdit() { + if (mInputMethodState != null) { + // XXX we should be smarter here, such as not doing invalidates + // until all edits are done. + mInputMethodState.mBatchEditing = true; + } + } + + /** + * Called by the framework in response to a request to end a batch + * of edit operations from the current input method, as a result of + * it calling {@link InputConnection#endBatchEdit + * InputConnection.endBatchEdit()}. The default implementation sets + * up the TextView's internal state to take care of this; if overriding + * you should call through to the super class. + */ + public void onEndBatchEdit() { + if (mInputMethodState != null) { + mInputMethodState.mBatchEditing = false; + // Cheezy way to get us to report the current cursor location. + invalidateCursor(); + } + } + + /** * Called by the framework in response to a private command from the * current method, provided by it calling * {@link InputConnection#performPrivateCommand @@ -5271,6 +5305,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mError != null) { hideError(); } + // Don't leave us in the middle of a batch edit. + onEndBatchEdit(); } startStopMarquee(focused); @@ -5300,6 +5336,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mBlink != null) { mBlink.cancel(); } + // Don't leave us in the middle of a batch edit. + onEndBatchEdit(); } startStopMarquee(hasWindowFocus); |