diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-18 17:39:46 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-18 17:39:46 -0700 |
commit | 105925376f8d0f6b318c9938c7b83ef7fef094da (patch) | |
tree | 3b19ee2bd8704cb9c6a0da7e42dec6759183de6d /core/java/android | |
parent | ba87e3e6c985e7175152993b5efcc7dd2f0e1c93 (diff) | |
download | frameworks_base-105925376f8d0f6b318c9938c7b83ef7fef094da.zip frameworks_base-105925376f8d0f6b318c9938c7b83ef7fef094da.tar.gz frameworks_base-105925376f8d0f6b318c9938c7b83ef7fef094da.tar.bz2 |
auto import from //branches/cupcake_rel/...@140373
Diffstat (limited to 'core/java/android')
57 files changed, 1695 insertions, 926 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index f49005e..cb1e903 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -3250,8 +3250,21 @@ public final class ActivityThread { r.window = null; r.hideForNow = false; r.nextIdle = null; - r.pendingResults = tmp.pendingResults; - r.pendingIntents = tmp.pendingIntents; + // Merge any pending results and pending intents; don't just replace them + if (tmp.pendingResults != null) { + if (r.pendingResults == null) { + r.pendingResults = tmp.pendingResults; + } else { + r.pendingResults.addAll(tmp.pendingResults); + } + } + if (tmp.pendingIntents != null) { + if (r.pendingIntents == null) { + r.pendingIntents = tmp.pendingIntents; + } else { + r.pendingIntents.addAll(tmp.pendingIntents); + } + } r.startsNotResumed = tmp.startsNotResumed; if (savedState != null) { r.state = savedState; diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 3b5ad86..ecef38f 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -103,6 +103,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.WeakHashMap; +import java.util.Set; +import java.util.HashSet; import java.util.Map.Entry; import org.xmlpull.v1.XmlPullParserException; @@ -2476,7 +2479,8 @@ class ApplicationContext extends Context { private final FileStatus mFileStatus = new FileStatus(); private long mTimestamp; - private List<OnSharedPreferenceChangeListener> mListeners; + private static final Object mContent = new Object(); + private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners; SharedPreferencesImpl( File file, int mode, Map initialContents) { @@ -2487,7 +2491,7 @@ class ApplicationContext extends Context { if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) { mTimestamp = mFileStatus.mtime; } - mListeners = new ArrayList<OnSharedPreferenceChangeListener>(); + mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); } public boolean hasFileChanged() { @@ -2509,9 +2513,7 @@ class ApplicationContext extends Context { public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { synchronized(this) { - if (!mListeners.contains(listener)) { - mListeners.add(listener); - } + mListeners.put(listener, mContent); } } @@ -2620,13 +2622,14 @@ class ApplicationContext extends Context { boolean hasListeners; List<String> keysModified = null; - List<OnSharedPreferenceChangeListener> listeners = null; + Set<OnSharedPreferenceChangeListener> listeners = null; synchronized (SharedPreferencesImpl.this) { hasListeners = mListeners.size() > 0; if (hasListeners) { keysModified = new ArrayList<String>(); - listeners = new ArrayList<OnSharedPreferenceChangeListener>(mListeners); + listeners = + new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { @@ -2635,9 +2638,7 @@ class ApplicationContext extends Context { mClear = false; } - Iterator<Entry<String, Object>> it = mModified.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<String, Object> e = it.next(); + for (Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); if (v == this) { @@ -2660,10 +2661,10 @@ class ApplicationContext extends Context { if (hasListeners) { for (int i = keysModified.size() - 1; i >= 0; i--) { final String key = keysModified.get(i); - // Call in the order they were registered - final int listenersSize = listeners.size(); - for (int j = 0; j < listenersSize; j++) { - listeners.get(j).onSharedPreferenceChanged(SharedPreferencesImpl.this, key); + for (OnSharedPreferenceChangeListener listener : listeners) { + if (listener != null) { + listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); + } } } } diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java index d6fcbb1..8d249da 100644 --- a/core/java/android/app/LauncherActivity.java +++ b/core/java/android/app/LauncherActivity.java @@ -65,6 +65,7 @@ public abstract class LauncherActivity extends ListActivity { public Drawable icon; public String packageName; public String className; + public Bundle extras; ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) { label = resolveInfo.loadLabel(pm); @@ -115,6 +116,9 @@ public abstract class LauncherActivity extends ListActivity { Intent intent = new Intent(mIntent); ListItem item = mActivitiesList.get(position); intent.setClassName(item.packageName, item.className); + if (item.extras != null) { + intent.putExtras(item.extras); + } return intent; } diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index a6a436f..d2fb605 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -278,11 +278,11 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** - * Stop the service, if the most recent time it was started was + * Stop the service if the most recent time it was started was * <var>startId</var>. This is the same as calling {@link * android.content.Context#stopService} for this particular service but allows you to * safely avoid stopping if there is a start request from a client that you - * haven't yet see in {@link #onStart}. + * haven't yet seen in {@link #onStart}. * * @param startId The most recent start identifier received in {@link * #onStart}. diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 3b10ed2..eca04b3 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -109,6 +109,23 @@ public class AppWidgetManager { public static final String EXTRA_APPWIDGET_IDS = "appWidgetIds"; /** + * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of + * {@link AppWidgetProviderInfo} objects to mix in to the list of AppWidgets that are + * installed. (This is how the launcher shows the search widget). + */ + public static final String EXTRA_CUSTOM_INFO = "customInfo"; + + /** + * An intent extra to pass to the AppWidget picker containing a {@link java.util.List} of + * {@link android.os.Bundle} objects to mix in to the list of AppWidgets that are + * installed. It will be added to the extras object on the {@link android.content.Intent} + * that is returned from the picker activity. + * + * {@more} + */ + public static final String EXTRA_CUSTOM_EXTRAS = "customExtras"; + + /** * A sentiel value that the AppWidget manager will never return as a appWidgetId. */ public static final int INVALID_APPWIDGET_ID = 0; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 1ba1c1e..abf08cb 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -31,6 +31,12 @@ import java.io.UnsupportedEncodingException; * @hide */ public class BluetoothDevice { + + public static final int BLUETOOTH_STATE_OFF = 0; + public static final int BLUETOOTH_STATE_TURNING_ON = 1; + public static final int BLUETOOTH_STATE_ON = 2; + public static final int BLUETOOTH_STATE_TURNING_OFF = 3; + /** Inquiry scan and page scan are both off. * Device is neither discoverable nor connectable */ public static final int SCAN_MODE_NONE = 0; @@ -83,7 +89,7 @@ public class BluetoothDevice { } /** - * Get the current status of Bluetooth hardware. + * Is Bluetooth currently turned on. * * @return true if Bluetooth enabled, false otherwise. */ @@ -95,37 +101,30 @@ public class BluetoothDevice { } /** + * Get the current state of Bluetooth. + * + * @return One of BLUETOOTH_STATE_ or BluetoothError.ERROR. + */ + public int getBluetoothState() { + try { + return mService.getBluetoothState(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return BluetoothError.ERROR; + } + + /** * Enable the Bluetooth device. * Turn on the underlying hardware. - * This is an asynchronous call, BluetoothIntent.ENABLED_ACTION will be - * sent if and when the device is successfully enabled. + * This is an asynchronous call, + * BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION can be used to check if + * and when the device is sucessfully enabled. * @return false if we cannot enable the Bluetooth device. True does not * imply the device was enabled, it only implies that so far there were no * problems. */ public boolean enable() { - return enable(null); - } - - /** - * Enable the Bluetooth device. - * Turns on the underlying hardware. - * This is an asynchronous call. onEnableResult() of your callback will be - * called when the call is complete, with either RESULT_SUCCESS or - * RESULT_FAILURE. - * - * Your callback will be called from a binder thread, not the main thread. - * - * In addition to the callback, BluetoothIntent.ENABLED_ACTION will be - * broadcast if the device is successfully enabled. - * - * @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. - */ - public boolean enable(IBluetoothDeviceCallback callback) { try { - return mService.enable(callback); + return mService.enable(); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -138,7 +137,7 @@ public class BluetoothDevice { */ public boolean disable() { try { - return mService.disable(); + return mService.disable(true); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } @@ -551,7 +550,6 @@ public class BluetoothDevice { } return pinBytes; } - private static final int ADDRESS_LENGTH = 17; /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */ diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java index 9273d0d..344601b 100644 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -41,6 +41,10 @@ public interface BluetoothIntent { "android.bluetooth.intent.RSSI"; public static final String CLASS = "android.bluetooth.intent.CLASS"; + public static final String BLUETOOTH_STATE = + "android.bluetooth.intent.BLUETOOTH_STATE"; + public static final String BLUETOOTH_PREVIOUS_STATE = + "android.bluetooth.intent.BLUETOOTH_PREVIOUS_STATE"; public static final String HEADSET_STATE = "android.bluetooth.intent.HEADSET_STATE"; public static final String HEADSET_PREVIOUS_STATE = @@ -54,12 +58,12 @@ public interface BluetoothIntent { public static final String REASON = "android.bluetooth.intent.REASON"; + /** Broadcast when the local Bluetooth device state changes, for example + * when Bluetooth is enabled. Will contain int extra's BLUETOOTH_STATE and + * BLUETOOTH_PREVIOUS_STATE. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ENABLED_ACTION = - "android.bluetooth.intent.action.ENABLED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String DISABLED_ACTION = - "android.bluetooth.intent.action.DISABLED"; + public static final String BLUETOOTH_STATE_CHANGED_ACTION = + "android.bluetooth.intent.action.BLUETOOTH_STATE_CHANGED"; @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String NAME_CHANGED_ACTION = diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl index 4351d2e..6cd792e 100644 --- a/core/java/android/bluetooth/IBluetoothDevice.aidl +++ b/core/java/android/bluetooth/IBluetoothDevice.aidl @@ -26,8 +26,9 @@ import android.bluetooth.IBluetoothDeviceCallback; interface IBluetoothDevice { boolean isEnabled(); - boolean enable(in IBluetoothDeviceCallback callback); // async - boolean disable(); + int getBluetoothState(); + boolean enable(); + boolean disable(boolean persistSetting); String getAddress(); String getName(); diff --git a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl index d25bd56..d057093 100644 --- a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl @@ -22,6 +22,4 @@ package android.bluetooth; oneway interface IBluetoothDeviceCallback { void onGetRemoteServiceChannelResult(in String address, int channel); - - void onEnableResult(int result); } diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java index a43a08b..1bf786f 100644 --- a/core/java/android/bluetooth/ScoSocket.java +++ b/core/java/android/bluetooth/ScoSocket.java @@ -76,7 +76,7 @@ public class ScoSocket { try { if (VDBG) log(this + " SCO OBJECT DTOR"); destroyNative(); - releaseWakeLock(); + releaseWakeLockNow(); } finally { super.finalize(); } @@ -98,7 +98,7 @@ public class ScoSocket { return true; } else { mState = STATE_CLOSED; - releaseWakeLock(); + releaseWakeLockNow(); return false; } } @@ -148,7 +148,7 @@ public class ScoSocket { mState = STATE_CLOSED; } mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget(); - releaseWakeLock(); + releaseWakeLockNow(); } private synchronized void onAccepted(int result) { @@ -183,7 +183,19 @@ public class ScoSocket { private void releaseWakeLock() { if (mWakeLock.isHeld()) { - if (VDBG) log("mWakeLock.release() " + this); + // Keep apps processor awake for a further 2 seconds. + // This is a hack to resolve issue http://b/1616263 - in which + // we are left in a 80 mA power state when remotely terminating a + // call while connected to BT headset "HTC BH S100 " with A2DP and + // HFP profiles. + if (VDBG) log("mWakeLock.release() in 2 sec" + this); + mWakeLock.acquire(2000); + } + } + + private void releaseWakeLockNow() { + if (mWakeLock.isHeld()) { + if (VDBG) log("mWakeLock.release() now" + this); mWakeLock.release(); } } diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 08f6191..a41627a 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -68,7 +68,7 @@ import android.util.Log; * these broadcasts, the Intent broadcast mechanism here is completely separate * from Intents that are used to start Activities with * {@link Context#startActivity Context.startActivity()}. - * There is no way for an BroadcastReceiver + * There is no way for a BroadcastReceiver * to see or capture Intents used with startActivity(); likewise, when * you broadcast an Intent, you will never find or start an Activity. * These two operations are semantically very different: starting an @@ -103,7 +103,7 @@ import android.util.Log; * its process before the asynchronous operation completes. * * <p>In particular, you may <i>not</i> show a dialog or bind to a service from - * within an BroadcastReceiver. For the former, you should instead use the + * within a BroadcastReceiver. For the former, you should instead use the * {@link android.app.NotificationManager} API. For the latter, you can * use {@link android.content.Context#startService Context.startService()} to * send a command to the service. @@ -141,7 +141,7 @@ import android.util.Log; * <a name="ProcessLifecycle"></a> * <h3>Process Lifecycle</h3> * - * <p>A process that is currently executing an BroadcastReceiver (that is, + * <p>A process that is currently executing a BroadcastReceiver (that is, * currently running the code in its {@link #onReceive} method) is * considered to be a foreground process and will be kept running by the * system except under cases of extreme memory pressure. @@ -156,7 +156,7 @@ import android.util.Log; * more important processes. * * <p>This means that for longer-running operations you will often use - * a {@link android.app.Service} in conjunction with an BroadcastReceiver to keep + * an {@link android.app.Service} in conjunction with a BroadcastReceiver to keep * the containing process active for the entire time of your operation. */ public abstract class BroadcastReceiver { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 90ff78a..306c02e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1035,7 +1035,7 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SEARCH_LONG_PRESS = "android.intent.action.SEARCH_LONG_PRESS"; - + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -1052,7 +1052,7 @@ public class Intent implements Parcelable { public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; /** - * Broadcast Action: Sent when the user is present after device wakes up (e.g when the + * Broadcast Action: Sent when the user is present after device wakes up (e.g when the * keyguard is gone). */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @@ -1210,7 +1210,7 @@ public class Intent implements Parcelable { /** * Broadcast Action: The charging state, or charge level of the battery has * changed. - * + * * <p class="note"> * You can <em>not</em> receive this through components declared * in manifests, only by exlicitly registering for it with @@ -1380,7 +1380,7 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED"; - + /** * Broadcast Action: An input method has been changed. * {@hide pending API Council approval} @@ -1450,12 +1450,12 @@ public class Intent implements Parcelable { * * <p>The Intent will have the following extra value: * <ul> - * <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> - + * <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> - * the phone number originally intended to be dialed.</li> * </ul> * <p>Once the broadcast is finished, the resultData is used as the actual * number to call. If <code>null</code>, no call will be placed.</p> - * <p>It is perfectly acceptable for multiple receivers to process the + * <p>It is perfectly acceptable for multiple receivers to process the * outgoing call in turn: for example, a parental control application * might verify that the user is authorized to place the call at that * time, then a number-rewriting application might add an area code if @@ -1463,7 +1463,7 @@ public class Intent implements Parcelable { * <p>For consistency, any receiver whose purpose is to prohibit phone * calls should have a priority of 0, to ensure it will see the final * phone number to be dialed. - * Any receiver whose purpose is to rewrite phone numbers to be called + * Any receiver whose purpose is to rewrite phone numbers to be called * should have a positive priority. * Negative priorities are reserved for the system for this broadcast; * using them may cause problems.</p> @@ -1472,7 +1472,7 @@ public class Intent implements Parcelable { * <p>Emergency calls cannot be intercepted using this mechanism, and * other calls cannot be modified to call emergency numbers using this * mechanism. - * <p>You must hold the + * <p>You must hold the * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS} * permission to receive this Intent.</p> */ @@ -1709,7 +1709,7 @@ public class Intent implements Parcelable { * implying that this is an update). */ public static final String EXTRA_DATA_REMOVED = "android.intent.extra.DATA_REMOVED"; - + /** * Used as a boolean extra field in {@link android.content.Intent#ACTION_PACKAGE_REMOVED} * intents to indicate that this is a replacement of the package, so this @@ -1717,7 +1717,7 @@ public class Intent implements Parcelable { * different version of the same package. */ public static final String EXTRA_REPLACING = "android.intent.extra.REPLACING"; - + /** * Used as an int extra field in {@link android.app.AlarmManager} intents * to tell the application being invoked how many pending alarms are being @@ -1728,15 +1728,6 @@ public class Intent implements Parcelable { */ public static final String EXTRA_ALARM_COUNT = "android.intent.extra.ALARM_COUNT"; - /** - * Used as an int extra field in {@link android.content.Intent#ACTION_VOICE_COMMAND} - * intents to request which audio route the voice command should prefer. - * The value should be a route from {@link android.media.AudioManager}, for - * example ROUTE_BLUETOOTH_SCO. Providing this value is optional. - * {@hide pending API Council approval} - */ - public static final String EXTRA_AUDIO_ROUTE = "android.intent.extra.AUDIO_ROUTE"; - // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). @@ -1781,7 +1772,7 @@ public class Intent implements Parcelable { * next task activity) defines an atomic group of activities that the * user can move to. Tasks can be moved to the foreground and background; * all of the activities inside of a particular task always remain in - * the same order. See + * the same order. See * <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application Fundamentals: * Activities and Tasks</a> for more details on tasks. * @@ -1812,7 +1803,7 @@ public class Intent implements Parcelable { * <p><strong>Because the default system does not include graphical task management, * you should not use this flag unless you provide some way for a user to * return back to the tasks you have launched.</strong> - * + * * <p>This flag is ignored if * {@link #FLAG_ACTIVITY_NEW_TASK} is not set. * @@ -1901,7 +1892,7 @@ public class Intent implements Parcelable { * the user re-launching it from home), this activity and all on top of * it will be finished so that the user does not return to them, but * instead returns to whatever activity preceeded it. - * + * * <p>This is useful for cases where you have a logical break in your * application. For example, an e-mail application may have a command * to view an attachment, which launches an image view activity to @@ -1919,31 +1910,31 @@ public class Intent implements Parcelable { * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint} * 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. + * activity does not think the user has acknowledged its notification. */ public static final int FLAG_ACTIVITY_NO_USER_ACTION = 0x00040000; /** * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, * this flag will cause the launched activity to be brought to the front of its * task's history stack if it is already running. - * + * * <p>For example, consider a task consisting of four activities: A, B, C, D. * If D calls startActivity() with an Intent that resolves to the component * of activity B, then B will be brought to the front of the history stack, * with this resulting order: A, C, D, B. - * + * * This flag will be ignored if {@link #FLAG_ACTIVITY_CLEAR_TOP} is also - * specified. + * specified. */ public static final int FLAG_ACTIVITY_REORDER_TO_FRONT = 0X00020000; /** @@ -1957,11 +1948,11 @@ public class Intent implements Parcelable { * will be launched. Sticky intent state will be recorded properly even * if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY} * is specified in the broadcast intent, this flag is unnecessary. - * + * * <p>This flag is only for use by system sevices as a convenience to * avoid having to implement a more complex mechanism around detection * of boot completion. - * + * * @hide */ public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000; @@ -2121,7 +2112,7 @@ public class Intent implements Parcelable { // old format Intent URI if (!uri.startsWith("#Intent;", i)) return getIntentOld(uri); - + // new format Intent intent = new Intent(ACTION_VIEW); @@ -2130,7 +2121,7 @@ public class Intent implements Parcelable { intent.mData = Uri.parse(uri.substring(0, i)); } i += "#Intent;".length(); - + // loop over contents of Intent, all name=value; while (!uri.startsWith("end", i)) { int eq = uri.indexOf('=', i); @@ -2161,7 +2152,7 @@ public class Intent implements Parcelable { else if (uri.startsWith("component=", i)) { intent.mComponent = ComponentName.unflattenFromString(value); } - + // extra else { String key = Uri.decode(uri.substring(i + 2, eq)); @@ -2181,18 +2172,18 @@ public class Intent implements Parcelable { else if (uri.startsWith("s.", i)) b.putShort(key, Short.parseShort(value)); else throw new URISyntaxException(uri, "unknown EXTRA type", i); } - + // move to the next item i = semi + 1; } return intent; - + } catch (IndexOutOfBoundsException e) { throw new URISyntaxException(uri, "illegal Intent URI format", i); } } - + public static Intent getIntentOld(String uri) throws URISyntaxException { Intent intent; @@ -2257,7 +2248,7 @@ public class Intent implements Parcelable { if (uri.regionMatches(i, "extras(", 0, 7)) { i += 7; - + final int closeParen = uri.indexOf(')', i); if (closeParen == -1) throw new URISyntaxException(uri, "EXTRA missing trailing ')'", i); @@ -2272,7 +2263,7 @@ public class Intent implements Parcelable { i++; String key = uri.substring(i, j); i = j + 1; - + // get type-value j = uri.indexOf('!', i); if (j == -1 || j >= closeParen) j = closeParen; @@ -2282,7 +2273,7 @@ public class Intent implements Parcelable { // create Bundle if it doesn't already exist if (intent.mExtras == null) intent.mExtras = new Bundle(); - + // add item to bundle try { switch (type) { @@ -2319,7 +2310,7 @@ public class Intent implements Parcelable { } catch (NumberFormatException e) { throw new URISyntaxException(uri, "EXTRA value can't be parsed", i); } - + char ch = uri.charAt(i); if (ch == ')') break; if (ch != '!') throw new URISyntaxException(uri, "EXTRA missing '!'", i); @@ -2522,7 +2513,7 @@ public class Intent implements Parcelable { public boolean hasFileDescriptors() { return mExtras != null && mExtras.hasFileDescriptors(); } - + /** * Retrieve extended data from the intent. * @@ -3889,7 +3880,7 @@ public class Intent implements Parcelable { /** * Completely replace the extras in the Intent with the extras in the * given Intent. - * + * * @param src The exact extras contained in this Intent are copied * into the target intent, replacing any that were previously there. */ @@ -3897,11 +3888,11 @@ public class Intent implements Parcelable { mExtras = src.mExtras != null ? new Bundle(src.mExtras) : null; return this; } - + /** * Completely replace the extras in the Intent with the given Bundle of * extras. - * + * * @param extras The new set of extras in the Intent, or null to erase * all extras. */ @@ -3909,7 +3900,7 @@ public class Intent implements Parcelable { mExtras = extras != null ? new Bundle(extras) : null; return this; } - + /** * Remove extended data from the intent. * @@ -4397,12 +4388,12 @@ public class Intent implements Parcelable { } } } - + uri.append("end"); return uri.toString(); } - + public int describeContents() { return (mExtras != null) ? mExtras.describeContents() : 0; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 7287d9c..3e94734 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -341,6 +341,14 @@ public abstract class PackageManager { public static final int INSTALL_FAILED_OLDER_SDK = -12; /** + * Installation return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} if + * the new package failed because it contains a content provider with the + * same authority as a provider already installed in the system. + */ + public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; + + /** * Installation parse return code: this is passed to the {@link IPackageInstallObserver} by * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} * if the parser was given a path that is not a file, or does not end with the expected diff --git a/core/java/android/emoji/EmojiFactory.java b/core/java/android/emoji/EmojiFactory.java index 389bd07..e0b12ae 100644 --- a/core/java/android/emoji/EmojiFactory.java +++ b/core/java/android/emoji/EmojiFactory.java @@ -253,6 +253,22 @@ public final class EmojiFactory { * is returned. */ public static native EmojiFactory newAvailableInstance(); + + /** + * Returns the lowest code point corresponding to an Android + * emoji character. + */ + public int getMinimumAndroidPua() { + return nativeGetMinimumAndroidPua(mNativeEmojiFactory); + } + + /** + * Returns the highest code point corresponding to an Android + * emoji character. + */ + public int getMaximumAndroidPua() { + return nativeGetMaximumAndroidPua(mNativeEmojiFactory); + } // native methods diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 32270c4..6ee92ce 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -22,8 +22,8 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import android.app.Dialog; import android.content.Context; import android.content.res.Configuration; +import android.content.res.TypedArray; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.IBinder; import android.os.ResultReceiver; @@ -44,6 +44,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; +import android.view.animation.AnimationUtils; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -54,6 +55,8 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.EditorInfo; import android.widget.Button; import android.widget.FrameLayout; +import android.widget.LinearLayout; + import java.io.FileDescriptor; import java.io.PrintWriter; @@ -204,6 +207,10 @@ import java.io.PrintWriter; * You can use these to reset and initialize your input state for the current * target. For example, you will often want to clear any input state, and * update a soft keyboard to be appropriate for the new inputType.</p> + * + * @attr ref android.R.styleable#InputMethodService_imeFullscreenBackground + * @attr ref android.R.styleable#InputMethodService_imeExtractEnterAnimation + * @attr ref android.R.styleable#InputMethodService_imeExtractExitAnimation */ public class InputMethodService extends AbstractInputMethodService { static final String TAG = "InputMethodService"; @@ -211,13 +218,19 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; + int mTheme = android.R.style.Theme_InputMethod; + LayoutInflater mInflater; + TypedArray mThemeAttrs; View mRootView; SoftInputWindow mWindow; boolean mInitialized; boolean mWindowCreated; boolean mWindowAdded; boolean mWindowVisible; + boolean mWindowWasVisible; + boolean mInShowWindow; + ViewGroup mFullscreenArea; FrameLayout mExtractFrame; FrameLayout mCandidatesFrame; FrameLayout mInputFrame; @@ -243,6 +256,7 @@ public class InputMethodService extends AbstractInputMethodService { boolean mFullscreenApplied; boolean mIsFullscreen; View mExtractView; + boolean mExtractViewHidden; ExtractEditText mExtractEditText; ViewGroup mExtractAccessories; Button mExtractAction; @@ -260,8 +274,8 @@ public class InputMethodService extends AbstractInputMethodService { final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = new ViewTreeObserver.OnComputeInternalInsetsListener() { public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) { - if (isFullscreenMode()) { - // In fullscreen mode, we just say the window isn't covering + if (isExtractViewShown()) { + // In true fullscreen mode, we just say the window isn't covering // any content so we don't impact whatever is behind. View decor = getWindow().getWindow().getDecorView(); info.contentInsets.top = info.visibleInsets.top @@ -519,12 +533,28 @@ public class InputMethodService extends AbstractInputMethodService { public int touchableInsets; } + /** + * You can call this to customize the theme used by your IME's window. + * This theme should typically be one that derives from + * {@link android.R.style#Theme_InputMethod}, which is the default theme + * you will get. This must be set before {@link #onCreate}, so you + * will typically call it in your constructor with the resource ID + * of your custom theme. + */ + public void setTheme(int theme) { + if (mWindow != null) { + throw new IllegalStateException("Must be called before onCreate()"); + } + mTheme = theme; + } + @Override public void onCreate() { + super.setTheme(mTheme); super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); - mWindow = new SoftInputWindow(this); + mWindow = new SoftInputWindow(this, mTheme); initViews(); mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT); } @@ -551,6 +581,7 @@ public class InputMethodService extends AbstractInputMethodService { mShowInputRequested = false; mShowInputForced = false; + mThemeAttrs = obtainStyledAttributes(android.R.styleable.InputMethodService); mRootView = mInflater.inflate( com.android.internal.R.layout.input_method, null); mWindow.setContentView(mRootView); @@ -560,6 +591,8 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.getWindow().setWindowAnimations( com.android.internal.R.style.Animation_InputMethodFancy); } + mFullscreenArea = (ViewGroup)mRootView.findViewById(com.android.internal.R.id.fullscreenArea); + mExtractViewHidden = false; mExtractFrame = (FrameLayout)mRootView.findViewById(android.R.id.extractArea); mExtractView = null; mExtractEditText = null; @@ -731,14 +764,20 @@ public class InputMethodService extends AbstractInputMethodService { if (ic != null) ic.reportFullscreenMode(isFullscreen); mFullscreenApplied = true; initialize(); - Drawable bg = onCreateBackgroundDrawable(); - if (bg == null) { - // We need to give the window a real drawable, so that it - // correctly sets its mode. - bg = getResources().getDrawable(android.R.color.transparent); + LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) + mFullscreenArea.getLayoutParams(); + if (isFullscreen) { + mFullscreenArea.setBackgroundDrawable(mThemeAttrs.getDrawable( + com.android.internal.R.styleable.InputMethodService_imeFullscreenBackground)); + lp.height = 0; + lp.weight = 1; + } else { + mFullscreenArea.setBackgroundDrawable(null); + lp.height = LinearLayout.LayoutParams.WRAP_CONTENT; + lp.weight = 0; } - mWindow.getWindow().setBackgroundDrawable(bg); - mExtractFrame.setVisibility(isFullscreen ? View.VISIBLE : View.GONE); + ((ViewGroup)mFullscreenArea.getParent()).updateViewLayout( + mFullscreenArea, lp); if (isFullscreen) { if (mExtractView == null) { View v = onCreateExtractTextView(); @@ -748,6 +787,7 @@ public class InputMethodService extends AbstractInputMethodService { } startExtractingText(false); } + updateExtractFrameVisibility(); } if (changed) { @@ -805,12 +845,66 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Controls the visibility of the extracted text area. This only applies + * when the input method is in fullscreen mode, and thus showing extracted + * text. When false, the extracted text will not be shown, allowing some + * of the application to be seen behind. This is normally set for you + * by {@link #onUpdateExtractingVisibility}. This controls the visibility + * of both the extracted text and candidate view; the latter since it is + * not useful if there is no text to see. + */ + public void setExtractViewShown(boolean shown) { + if (mExtractViewHidden == shown) { + mExtractViewHidden = !shown; + updateExtractFrameVisibility(); + } + } + + /** + * Return whether the fullscreen extract view is shown. This will only + * return true if {@link #isFullscreenMode()} returns true, and in that + * case its value depends on the last call to + * {@link #setExtractViewShown(boolean)}. This effectively lets you + * determine if the application window is entirely covered (when this + * returns true) or if some part of it may be shown (if this returns + * false, though if {@link #isFullscreenMode()} returns true in that case + * then it is probably only a sliver of the application). + */ + public boolean isExtractViewShown() { + return mIsFullscreen && !mExtractViewHidden; + } + + void updateExtractFrameVisibility() { + int vis; + if (isFullscreenMode()) { + vis = mExtractViewHidden ? View.INVISIBLE : View.VISIBLE; + mExtractFrame.setVisibility(View.VISIBLE); + } else { + vis = View.VISIBLE; + mExtractFrame.setVisibility(View.GONE); + } + updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); + if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) { + int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE + ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation + : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation, + 0); + if (animRes != 0) { + mFullscreenArea.startAnimation(AnimationUtils.loadAnimation( + this, animRes)); + } + } + mFullscreenArea.setVisibility(vis); + } + + /** * Compute the interesting insets into your UI. The default implementation * uses the top of the candidates frame for the visible insets, and the * top of the input frame for the content insets. The default touchable * insets are {@link Insets#TOUCHABLE_INSETS_VISIBLE}. * - * <p>Note that this method is not called when in fullscreen mode, since + * <p>Note that this method is not called when + * {@link #isExtractViewShown} returns true, since * in that case the application is left as-is behind the input method and * not impacted by anything in its UI. * @@ -821,9 +915,16 @@ public class InputMethodService extends AbstractInputMethodService { if (mInputFrame.getVisibility() == View.VISIBLE) { mInputFrame.getLocationInWindow(loc); } else { - loc[1] = 0; + View decor = getWindow().getWindow().getDecorView(); + loc[1] = decor.getHeight(); + } + if (isFullscreenMode()) { + // In fullscreen mode, we never resize the underlying window. + View decor = getWindow().getWindow().getDecorView(); + outInsets.contentTopInsets = decor.getHeight(); + } else { + outInsets.contentTopInsets = loc[1]; } - outInsets.contentTopInsets = loc[1]; if (mCandidatesFrame.getVisibility() == View.VISIBLE) { mCandidatesFrame.getLocationInWindow(loc); } @@ -889,11 +990,7 @@ public class InputMethodService extends AbstractInputMethodService { * it is hidden. */ public void setCandidatesViewShown(boolean shown) { - int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); - if (mCandidatesVisibility != vis) { - mCandidatesFrame.setVisibility(vis); - mCandidatesVisibility = vis; - } + updateCandidatesVisibility(shown); if (!mShowInputRequested && mWindowVisible != shown) { // If we are being asked to show the candidates view while the app // has not asked for the input view to be shown, then we need @@ -906,17 +1003,26 @@ public class InputMethodService extends AbstractInputMethodService { } } + void updateCandidatesVisibility(boolean shown) { + int vis = shown ? View.VISIBLE : getCandidatesHiddenVisibility(); + if (mCandidatesVisibility != vis) { + mCandidatesFrame.setVisibility(vis); + mCandidatesVisibility = vis; + } + } + /** * Returns the visibility mode (either {@link View#INVISIBLE View.INVISIBLE} * or {@link View#GONE View.GONE}) of the candidates view when it is not - * shown. The default implementation returns GONE when in fullscreen mode, + * shown. The default implementation returns GONE when + * {@link #isExtractViewShown} returns true, * otherwise VISIBLE. Be careful if you change this to return GONE in * other situations -- if showing or hiding the candidates view causes * your window to resize, this can cause temporary drawing artifacts as * the resize takes place. */ public int getCandidatesHiddenVisibility() { - return isFullscreenMode() ? View.GONE : View.INVISIBLE; + return isExtractViewShown() ? View.GONE : View.INVISIBLE; } public void showStatusIcon(int iconResId) { @@ -992,20 +1098,6 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Called by the framework to create a Drawable for the background of - * the input method window. May return null for no background. The default - * implementation returns a non-null standard background only when in - * fullscreen mode. This is called each time the fullscreen mode changes. - */ - public Drawable onCreateBackgroundDrawable() { - if (isFullscreenMode()) { - return getResources().getDrawable( - com.android.internal.R.drawable.input_method_fullscreen_background); - } - return null; - } - - /** * Called by the framework to create the layout for showing extacted text. * Only called when in fullscreen mode. The returned view hierarchy must * have an {@link ExtractEditText} whose ID is @@ -1174,6 +1266,23 @@ public class InputMethodService extends AbstractInputMethodService { + " mWindowCreated=" + mWindowCreated + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted); + + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + try { + mWindowWasVisible = mWindowVisible; + mInShowWindow = true; + showWindowInner(showInput); + } finally { + mWindowWasVisible = true; + mInShowWindow = false; + } + } + + void showWindowInner(boolean showInput) { boolean doShowInput = false; boolean wasVisible = mWindowVisible; mWindowVisible = true; @@ -1241,6 +1350,7 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.hide(); mWindowVisible = false; onWindowHidden(); + mWindowWasVisible = false; } } @@ -1559,7 +1669,7 @@ public class InputMethodService extends AbstractInputMethodService { boolean doMovementKey(int keyCode, KeyEvent event, int count) { final ExtractEditText eet = mExtractEditText; - if (isFullscreenMode() && isInputViewShown() && eet != null) { + if (isExtractViewShown() && isInputViewShown() && eet != null) { // If we are in fullscreen mode, the cursor will move around // the extract edit text, but should NOT cause focus to move // to other fields. @@ -1583,10 +1693,10 @@ public class InputMethodService extends AbstractInputMethodService { if (movement.onKeyOther(eet, (Spannable)eet.getText(), event)) { reportExtractedMovement(keyCode, count); } else { - KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); if (movement.onKeyDown(eet, (Spannable)eet.getText(), keyCode, down)) { - KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); + KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); movement.onKeyUp(eet, (Spannable)eet.getText(), keyCode, up); while (--count > 0) { @@ -1800,18 +1910,52 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * Called when it is time to update the actions available from a full-screen - * IME. You do not need to deal with this if you are using the standard + * Called when the fullscreen-mode extracting editor info has changed, + * to determine whether the extracting (extract text and candidates) portion + * of the UI should be shown. The standard implementation hides or shows + * the extract area depending on whether it makes sense for the + * current editor. In particular, a {@link InputType#TYPE_NULL} + * input type or {@link EditorInfo#IME_FLAG_NO_EXTRACT_UI} flag will + * turn off the extract area since there is no text to be shown. + */ + public void onUpdateExtractingVisibility(EditorInfo ei) { + if (ei.inputType == InputType.TYPE_NULL || + (ei.imeOptions&EditorInfo.IME_FLAG_NO_EXTRACT_UI) != 0) { + // No reason to show extract UI! + setExtractViewShown(false); + return; + } + + setExtractViewShown(true); + } + + /** + * Called when the fullscreen-mode extracting editor info has changed, + * to update the state of its UI such as the action buttons shown. + * You do not need to deal with this if you are using the standard * full screen extract UI. If replacing it, you will need to re-implement - * this to put the action in your own UI and handle it. + * this to put the appropriate action button in your own UI and handle it, + * and perform any other changes. + * + * <p>The standard implementation turns on or off its accessory area + * depending on whether there is an action button, and hides or shows + * the entire extract area depending on whether it makes sense for the + * current editor. In particular, a {@link InputType#TYPE_NULL} or + * {@link InputType#TYPE_TEXT_VARIATION_FILTER} input type will turn off the + * extract area since there is no text to be shown. */ - public void onUpdateExtractingAccessories(EditorInfo ei) { + public void onUpdateExtractingViews(EditorInfo ei) { + if (!isExtractViewShown()) { + return; + } + if (mExtractAccessories == null) { return; } final boolean hasAction = ei.actionLabel != null || ( (ei.imeOptions&EditorInfo.IME_MASK_ACTION) != EditorInfo.IME_ACTION_NONE && - (ei.imeOptions&EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0); + (ei.imeOptions&EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION) == 0 && + ei.inputType != InputType.TYPE_NULL); if (hasAction) { mExtractAccessories.setVisibility(View.VISIBLE); if (ei.actionLabel != null) { @@ -1855,7 +1999,8 @@ public class InputMethodService extends AbstractInputMethodService { try { eet.startInternalChanges(); - onUpdateExtractingAccessories(ei); + onUpdateExtractingVisibility(ei); + onUpdateExtractingViews(ei); int inputType = ei.inputType; if ((inputType&EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { @@ -1890,8 +2035,10 @@ public class InputMethodService extends AbstractInputMethodService { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); p.println(" mWindowCreated=" + mWindowCreated - + " mWindowAdded=" + mWindowAdded - + " mWindowVisible=" + mWindowVisible); + + " mWindowAdded=" + mWindowAdded); + p.println(" mWindowVisible=" + mWindowVisible + + " mWindowWasVisible=" + mWindowWasVisible + + " mInShowWindow=" + mInShowWindow); p.println(" Configuration=" + getResources().getConfiguration()); p.println(" mToken=" + mToken); p.println(" mInputBinding=" + mInputBinding); @@ -1914,7 +2061,8 @@ public class InputMethodService extends AbstractInputMethodService { + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags)); p.println(" mCandidatesVisibility=" + mCandidatesVisibility + " mFullscreenApplied=" + mFullscreenApplied - + " mIsFullscreen=" + mIsFullscreen); + + " mIsFullscreen=" + mIsFullscreen + + " mExtractViewHidden=" + mExtractViewHidden); if (mExtractedText != null) { p.println(" mExtractedText:"); diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index c37845f..d91ace6 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -31,18 +31,6 @@ import android.view.WindowManager; */ class SoftInputWindow extends Dialog { - /** - * Create a DockWindow that uses the default style. - * - * @param context The Context the DockWindow is to run it. In particular, it - * uses the window manager and theme in this context to present its - * UI. - */ - public SoftInputWindow(Context context) { - super(context, com.android.internal.R.style.Theme_InputMethod); - initDockWindow(); - } - public void setToken(IBinder token) { WindowManager.LayoutParams lp = getWindow().getAttributes(); lp.token = token; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 7590bfe..76c74df 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -4,6 +4,8 @@ import java.io.PrintWriter; import java.util.Formatter; import java.util.Map; +import com.android.internal.os.BatteryStatsImpl.Timer; + import android.util.Log; import android.util.Printer; import android.util.SparseArray; @@ -39,6 +41,20 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public static final int SENSOR = 3; + + /** + * A constant indicating a full wifi lock timer + * + * {@hide} + */ + public static final int FULL_WIFI_LOCK = 4; + + /** + * A constant indicating a scan wifi lock timer + * + * {@hide} + */ + public static final int SCAN_WIFI_LOCK = 5; /** * Include all of the data in the stats, including previously saved data. @@ -74,6 +90,7 @@ public abstract class BatteryStats implements Parcelable { private static final String WAKELOCK_DATA = "wakelock"; private static final String NETWORK_DATA = "network"; private static final String BATTERY_DATA = "battery"; + private static final String WIFI_LOCK_DATA = "wifilock"; private static final String MISC_DATA = "misc"; private final StringBuilder mFormatBuilder = new StringBuilder(8); @@ -162,6 +179,13 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract long getTcpBytesSent(int which); + + public abstract void noteFullWifiLockAcquiredLocked(); + public abstract void noteFullWifiLockReleasedLocked(); + public abstract void noteScanWifiLockAcquiredLocked(); + public abstract void noteScanWifiLockReleasedLocked(); + public abstract long getFullWifiLockTime(long batteryRealtime, int which); + public abstract long getScanWifiLockTime(long batteryRealtime, int which); public static abstract class Sensor { // Magic sensor number for the GPS. @@ -270,6 +294,22 @@ public abstract class BatteryStats implements Parcelable { public abstract long getPhoneOnTime(long batteryRealtime, int which); /** + * Returns the time in milliseconds that wifi has been on while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getWifiOnTime(long batteryRealtime, int which); + + /** + * Returns the time in milliseconds that bluetooth has been on while the device was + * running on battery. + * + * {@hide} + */ + public abstract long getBluetoothOnTime(long batteryRealtime, int which); + + /** * Return whether we are currently running on battery. */ public abstract boolean getIsOnBattery(); @@ -292,6 +332,17 @@ public abstract class BatteryStats implements Parcelable { * @param curTime the amount of elapsed realtime in microseconds. */ public abstract long getBatteryRealtime(long curTime); + + /** + * Returns the battery percentage level at the last time the device was unplugged from power, + * or the last time it was booted while unplugged. + */ + public abstract int getUnpluggedStartLevel(); + + /** + * Returns the battery percentage level at the last time the device was plugged into power. + */ + public abstract int getPluggedStartLevel(); /** * Returns the total, last, or current battery uptime in microseconds. @@ -483,6 +534,8 @@ public abstract class BatteryStats implements Parcelable { final long totalUptime = computeUptime(rawUptime, which); final long screenOnTime = getScreenOnTime(batteryRealtime, which); final long phoneOnTime = getPhoneOnTime(batteryRealtime, which); + final long wifiOnTime = getWifiOnTime(batteryRealtime, which); + final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which); StringBuilder sb = new StringBuilder(128); @@ -496,7 +549,12 @@ public abstract class BatteryStats implements Parcelable { // Dump misc stats dumpLine(pw, 0 /* uid */, category, MISC_DATA, - screenOnTime / 1000, phoneOnTime / 1000); + screenOnTime / 1000, phoneOnTime / 1000, wifiOnTime / 1000, bluetoothOnTime / 1000); + + if (which == STATS_UNPLUGGED) { + dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, getUnpluggedStartLevel(), + getPluggedStartLevel()); + } SparseArray<? extends Uid> uidStats = getUidStats(); final int NU = uidStats.size(); @@ -506,7 +564,15 @@ public abstract class BatteryStats implements Parcelable { // Dump Network stats per uid, if any long rx = u.getTcpBytesReceived(which); long tx = u.getTcpBytesSent(which); + long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); + long scanWifiLockOnTime = u.getScanWifiLockTime(batteryRealtime, which); + if (rx > 0 || tx > 0) dumpLine(pw, uid, category, NETWORK_DATA, rx, tx); + + if (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0) { + dumpLine(pw, uid, category, WIFI_LOCK_DATA, + fullWifiLockOnTime, scanWifiLockOnTime); + } Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { @@ -624,13 +690,35 @@ public abstract class BatteryStats implements Parcelable { + formatTimeMs(totalRealtime / 1000) + "realtime"); - long screenOnTime = getScreenOnTime(batteryRealtime, which); - long phoneOnTime = getPhoneOnTime(batteryRealtime, which); + final long screenOnTime = getScreenOnTime(batteryRealtime, which); + final long phoneOnTime = getPhoneOnTime(batteryRealtime, which); + final long wifiOnTime = getWifiOnTime(batteryRealtime, which); + final long bluetoothOnTime = getBluetoothOnTime(batteryRealtime, which); pw.println(prefix + " Time with screen on: " + formatTimeMs(screenOnTime / 1000) + "(" + formatRatioLocked(screenOnTime, whichBatteryRealtime) + "), time with phone on: " + formatTimeMs(phoneOnTime / 1000) - + "(" + formatRatioLocked(phoneOnTime, whichBatteryRealtime) + ")"); + + "(" + formatRatioLocked(phoneOnTime, whichBatteryRealtime) + + "), time with wifi on: " + formatTimeMs(wifiOnTime / 1000) + + "(" + formatRatioLocked(wifiOnTime, whichBatteryRealtime) + + "), time with bluetooth on: " + formatTimeMs(bluetoothOnTime / 1000) + + "(" + formatRatioLocked(bluetoothOnTime, whichBatteryRealtime)+ ")"); + + pw.println(" "); + + if (which == STATS_UNPLUGGED) { + if (getIsOnBattery()) { + pw.println(prefix + " Device is currently unplugged"); + pw.println(prefix + " Discharge cycle start level: " + + getUnpluggedStartLevel()); + } else { + pw.println(prefix + " Device is currently plugged into power"); + pw.println(prefix + " Last discharge cycle start level: " + + getUnpluggedStartLevel()); + pw.println(prefix + " Last discharge cycle end level: " + + getPluggedStartLevel()); + } + } pw.println(" "); @@ -644,10 +732,23 @@ public abstract class BatteryStats implements Parcelable { long tcpReceived = u.getTcpBytesReceived(which); long tcpSent = u.getTcpBytesSent(which); + long fullWifiLockOnTime = u.getFullWifiLockTime(batteryRealtime, which); + long scanWifiLockOnTime = u.getScanWifiLockTime(batteryRealtime, which); + if (tcpReceived != 0 || tcpSent != 0) { pw.println(prefix + " Network: " + tcpReceived + " bytes received, " + tcpSent + " bytes sent"); } + if (fullWifiLockOnTime != 0 || scanWifiLockOnTime != 0) { + pw.println(prefix + " Full Wifi Lock Time: " + + formatTime(fullWifiLockOnTime / 1000) + + "(" + formatRatioLocked(fullWifiLockOnTime, + whichBatteryRealtime)+ ")"); + pw.println(prefix + " Scan Wifi Lock Time: " + + formatTime(scanWifiLockOnTime / 1000) + + "(" + formatRatioLocked(scanWifiLockOnTime, + whichBatteryRealtime)+ ")"); + } Map<String, ? extends BatteryStats.Uid.Wakelock> wakelocks = u.getWakelockStats(); if (wakelocks.size() > 0) { diff --git a/core/java/android/os/Hardware.java b/core/java/android/os/Hardware.java index 3b6c9d7..efc5617 100644 --- a/core/java/android/os/Hardware.java +++ b/core/java/android/os/Hardware.java @@ -21,22 +21,29 @@ package android.os; */ public class Hardware { - /** - * Control the LED. - */ - public static native int setLedState(int colorARGB, int onMS, int offMS); - - /** - * Control the Flashlight - */ + + + /* ******************************************************************************** + * + * + * + * + * + * + * + * + * Don't add anything else to this class. Add it to HardwareService instead. + * + * + * + * + * + * + * + * ********************************************************************************/ + + public static native boolean getFlashlightEnabled(); public static native void setFlashlightEnabled(boolean on); public static native void enableCameraFlash(int milliseconds); - - /** - * Control the backlights - */ - public static native void setScreenBacklight(int brightness); - public static native void setKeyboardBacklight(boolean on); - public static native void setButtonBacklight(boolean on); } diff --git a/core/java/android/os/IHardwareService.aidl b/core/java/android/os/IHardwareService.aidl index 4f6029f..fb121bb 100755 --- a/core/java/android/os/IHardwareService.aidl +++ b/core/java/android/os/IHardwareService.aidl @@ -29,12 +29,10 @@ interface IHardwareService void setFlashlightEnabled(boolean on); void enableCameraFlash(int milliseconds); - // backlight support - void setScreenBacklight(int brightness); - void setKeyboardBacklight(boolean on); - void setButtonBacklight(boolean on); - - // LED support - void setLedState(int colorARGB, int onMS, int offMS); + // sets the brightness of the backlights (screen, keyboard, button) 0-255 + void setBacklights(int brightness); + + // for the phone + void setAttentionLight(boolean on); } diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index b53e227..47497e5 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -45,26 +45,6 @@ public class Power public static native void releaseWakeLock(String id); /** - * Flag to turn on and off the keyboard light. - */ - public static final int KEYBOARD_LIGHT = 0x00000001; - - /** - * Flag to turn on and off the screen backlight. - */ - public static final int SCREEN_LIGHT = 0x00000002; - - /** - * Flag to turn on and off the button backlight. - */ - public static final int BUTTON_LIGHT = 0x00000004; - - /** - * Flags to turn on and off all the backlights. - */ - public static final int ALL_LIGHTS = (KEYBOARD_LIGHT|SCREEN_LIGHT|BUTTON_LIGHT); - - /** * Brightness value for fully off */ public static final int BRIGHTNESS_OFF = 0; @@ -91,14 +71,6 @@ public class Power public static final int LOW_BATTERY_THRESHOLD = 10; /** - * Set the brightness for one or more lights - * - * @param mask flags indicating which lights to change brightness - * @param brightness new brightness value (0 = off, 255 = fully bright) - */ - public static native int setLightBrightness(int mask, int brightness); - - /** * Turn the screen on or off * * @param on Whether you want the screen on or off diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 04e7ef0..63f6dff 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -211,7 +211,7 @@ public class RemoteCallbackList<E extends IInterface> { for (Callback cb : mCallbacks.values()) { active[i++] = cb.mCallback; } - return N; + return i; } } diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index d75a25f..4a709f6 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -1054,6 +1054,9 @@ public final class Calendar { if (cursor == null) { return; } + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "missed alarms found: " + cursor.getCount()); + } try { while (cursor.moveToNext()) { @@ -1068,6 +1071,8 @@ public final class Calendar { intent.putExtra(android.provider.Calendar.EVENT_END_TIME, end); PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + Log.w(TAG, "rescheduling missed alarm, id: " + id + " begin: " + begin + + " end: " + end + " alarmTime: " + alarmTime); manager.set(AlarmManager.RTC_WAKEUP, alarmTime, sender); } } finally { diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 10fe3f5..abd6934 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -22,6 +22,8 @@ import android.content.Context; import android.net.Uri; import android.provider.Contacts.People; import com.android.internal.telephony.CallerInfo; +import com.android.internal.telephony.Connection; + import android.text.TextUtils; import android.util.Log; @@ -137,7 +139,8 @@ public class CallLog { * if the contact is unknown. * @param context the context used to get the ContentResolver * @param number the phone number to be added to the calls db - * @param isPrivateNumber <code>true</code> if the call was marked as private by the network + * @param presentation the number presenting rules set by the network for + * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" * @param start time stamp for the call in milliseconds * @param duration call duration in seconds @@ -145,12 +148,14 @@ public class CallLog { * {@hide} */ public static Uri addCall(CallerInfo ci, Context context, String number, - boolean isPrivateNumber, int callType, long start, int duration) { + int presentation, int callType, long start, int duration) { final ContentResolver resolver = context.getContentResolver(); if (TextUtils.isEmpty(number)) { - if (isPrivateNumber) { + if (presentation == Connection.PRESENTATION_RESTRICTED) { number = CallerInfo.PRIVATE_NUMBER; + } else if (presentation == Connection.PRESENTATION_PAYPHONE) { + number = CallerInfo.PAYPHONE_NUMBER; } else { number = CallerInfo.UNKNOWN_NUMBER; } diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 2aa77ea..be31c0a 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -1459,13 +1459,24 @@ public class Contacts { "com.android.contacts.action.SHOW_OR_CREATE_CONTACT"; /** - * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new contact if no matching - * contact found. Otherwise, default behavior is to prompt user with dialog before creating. - * - * <P>Type: BOOLEAN</P> + * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new + * contact if no matching contact found. Otherwise, default behavior is + * to prompt user with dialog before creating. + * <p> + * Type: BOOLEAN */ public static final String EXTRA_FORCE_CREATE = "com.android.contacts.action.FORCE_CREATE"; + + /** + * Used with {@link #SHOW_OR_CREATE_CONTACT} to specify an exact + * description to be shown when prompting user about creating a new + * contact. + * <p> + * Type: STRING + */ + public static final String EXTRA_CREATE_DESCRIPTION = + "com.android.contacts.action.CREATE_DESCRIPTION"; /** * Intents related to the Contacts app UI. diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 3aa4078..3c50707 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -63,6 +63,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final IntentFilter mIntentFilter; private HashMap<String, SinkState> mAudioDevices; private final AudioManager mAudioManager; + private final BluetoothDevice mBluetooth; private class SinkState { public String address; @@ -75,9 +76,8 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - BluetoothDevice device = - (BluetoothDevice)mContext.getSystemService(Context.BLUETOOTH_SERVICE); - if (device == null) { + mBluetooth = (BluetoothDevice) mContext.getSystemService(Context.BLUETOOTH_SERVICE); + if (mBluetooth == null) { throw new RuntimeException("Platform does not support Bluetooth"); } @@ -85,13 +85,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { throw new RuntimeException("Could not init BluetoothA2dpService"); } - mIntentFilter = new IntentFilter(BluetoothIntent.ENABLED_ACTION); - mIntentFilter.addAction(BluetoothIntent.DISABLED_ACTION); + mIntentFilter = new IntentFilter(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION); mIntentFilter.addAction(BluetoothIntent.BOND_STATE_CHANGED_ACTION); mIntentFilter.addAction(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); mContext.registerReceiver(mReceiver, mIntentFilter); - if (device.isEnabled()) { + if (mBluetooth.isEnabled()) { onBluetoothEnable(); } } @@ -110,10 +109,17 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); String address = intent.getStringExtra(BluetoothIntent.ADDRESS); - if (action.equals(BluetoothIntent.ENABLED_ACTION)) { - onBluetoothEnable(); - } else if (action.equals(BluetoothIntent.DISABLED_ACTION)) { - onBluetoothDisable(); + if (action.equals(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION)) { + int state = intent.getIntExtra(BluetoothIntent.BLUETOOTH_STATE, + BluetoothError.ERROR); + switch (state) { + case BluetoothDevice.BLUETOOTH_STATE_ON: + onBluetoothEnable(); + break; + case BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF: + onBluetoothDisable(); + break; + } } else if (action.equals(BluetoothIntent.BOND_STATE_CHANGED_ACTION)) { int bondState = intent.getIntExtra(BluetoothIntent.BOND_STATE, BluetoothError.ERROR); @@ -145,9 +151,10 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { switch (msg.what) { case MESSAGE_CONNECT_TO: String address = (String)msg.obj; - // check device is still preferred, and nothing is currently - // connected - if (getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF && + // check bluetooth is still on, device is still preferred, and + // nothing is currently connected + if (mBluetooth.isEnabled() && + getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF && lookupSinksMatchingStates(new int[] { BluetoothA2dp.STATE_CONNECTING, BluetoothA2dp.STATE_CONNECTED, diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 9e9ba62..b7e3846 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -37,8 +37,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.Handler; +import android.os.IBinder; import android.os.Message; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemService; import android.provider.Settings; import android.util.Log; @@ -51,6 +53,8 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import com.android.internal.app.IBatteryStats; + public class BluetoothDeviceService extends IBluetoothDevice.Stub { private static final String TAG = "BluetoothDeviceService"; private static final boolean DBG = true; @@ -60,14 +64,18 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { 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 int mBluetoothState; private boolean mIsDiscovering; + private final IBatteryStats mBatteryStats; private final Context mContext; private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; + private static final int MESSAGE_REGISTER_SDP_RECORDS = 1; + private static final int MESSAGE_FINISH_DISABLE = 2; + static { classInitNative(); } @@ -75,6 +83,12 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public BluetoothDeviceService(Context context) { mContext = context; + + // Need to do this in place of: + // mBatteryStats = BatteryStatsService.getService(); + // Since we can not import BatteryStatsService from here. This class really needs to be + // moved to java/services/com/android/server/ + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); } /** Must be called after construction, and before any other method. @@ -87,7 +101,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { disableNative(); } - mIsEnabled = false; + setBluetoothState(BluetoothDevice.BLUETOOTH_STATE_OFF); mIsDiscovering = false; mEventLoop = new BluetoothEventLoop(mContext, this); registerForAirplaneMode(); @@ -109,10 +123,16 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { public boolean isEnabled() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); - return mIsEnabled; + return mBluetoothState == BluetoothDevice.BLUETOOTH_STATE_ON; } private native int isEnabledNative(); + public int getBluetoothState() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + return mBluetoothState; + } + + /** * Bring down bluetooth and disable BT in settings. Returns true on success. */ @@ -124,17 +144,36 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { * Bring down bluetooth. Returns true on success. * * @param saveSetting If true, disable BT in settings - * */ public synchronized boolean disable(boolean saveSetting) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); + switch (mBluetoothState) { + case BluetoothDevice.BLUETOOTH_STATE_OFF: + return true; + case BluetoothDevice.BLUETOOTH_STATE_ON: + break; + default: + return false; + } if (mEnableThread != null && mEnableThread.isAlive()) { return false; } - if (!mIsEnabled) { - return true; + setBluetoothState(BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF); + + // Allow 3 seconds for profiles to gracefully disconnect + // TODO: Introduce a callback mechanism so that each profile can notify + // BluetoothDeviceService when it is done shutting down + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0), 3000); + return true; + } + + + private synchronized void finishDisable(boolean saveSetting) { + if (mBluetoothState != BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) { + return; } mEventLoop.stop(); disableNative(); @@ -163,38 +202,37 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { intent.putExtra(BluetoothIntent.SCAN_MODE, BluetoothDevice.SCAN_MODE_NONE); mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mIsEnabled = false; + mIsDiscovering = false; + if (saveSetting) { persistBluetoothOnSetting(false); } - mIsDiscovering = false; - intent = new Intent(BluetoothIntent.DISABLED_ACTION); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - return true; + + setBluetoothState(BluetoothDevice.BLUETOOTH_STATE_OFF); + + // Log bluetooth off to battery stats. + long ident = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteBluetoothOff(); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } } - /** - * Bring up bluetooth, asynchronously, and enable BT in settings. - * This turns on/off the underlying hardware. - * - * @return True on success (so far), guaranteeing the callback with be - * notified when complete. - */ - public boolean enable(IBluetoothDeviceCallback callback) { - return enable(callback, true); + /** Bring up BT and persist BT on in settings */ + public boolean enable() { + return enable(true); } /** * Enable this Bluetooth device, asynchronously. * This turns on/off the underlying hardware. * - * @param saveSetting If true, enable BT in settings - * - * @return True on success (so far), guaranteeing the callback with be - * notified when complete. + * @param saveSetting If true, persist the new state of BT in settings + * @return True on success (so far) */ - public synchronized boolean enable(IBluetoothDeviceCallback callback, - boolean saveSetting) { + public synchronized boolean enable(boolean saveSetting) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); @@ -202,28 +240,49 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { if (mIsAirplaneSensitive && isAirplaneModeOn()) { return false; } - if (mIsEnabled) { + if (mBluetoothState != BluetoothDevice.BLUETOOTH_STATE_OFF) { return false; } if (mEnableThread != null && mEnableThread.isAlive()) { return false; } - mEnableThread = new EnableThread(callback, saveSetting); + setBluetoothState(BluetoothDevice.BLUETOOTH_STATE_TURNING_ON); + mEnableThread = new EnableThread(saveSetting); mEnableThread.start(); return true; } - private static final int REGISTER_SDP_RECORDS = 1; + private synchronized void setBluetoothState(int state) { + if (state == mBluetoothState) { + return; + } + + if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state); + + Intent intent = new Intent(BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION); + intent.putExtra(BluetoothIntent.BLUETOOTH_PREVIOUS_STATE, mBluetoothState); + intent.putExtra(BluetoothIntent.BLUETOOTH_STATE, state); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + + mBluetoothState = state; + + mContext.sendBroadcast(intent, BLUETOOTH_PERM); + } + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { - case REGISTER_SDP_RECORDS: + case MESSAGE_REGISTER_SDP_RECORDS: //TODO: Don't assume HSP/HFP is running, don't use sdptool, if (isEnabled()) { SystemService.start("hsag"); SystemService.start("hfag"); } + break; + case MESSAGE_FINISH_DISABLE: + finishDisable(msg.arg1 != 0); + break; } } }; @@ -231,10 +290,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { private EnableThread mEnableThread; private class EnableThread extends Thread { - private final IBluetoothDeviceCallback mEnableCallback; private final boolean mSaveSetting; - public EnableThread(IBluetoothDeviceCallback callback, boolean saveSetting) { - mEnableCallback = callback; + public EnableThread(boolean saveSetting) { mSaveSetting = saveSetting; } public void run() { @@ -244,7 +301,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { boolean running = false; while ((retryCount-- > 0) && !running) { mEventLoop.start(); - // it may take a momement for the other thread to do its + // it may take a momement for the other thread to do its // thing. Check periodically for a while. int pollCount = 5; while ((pollCount-- > 0) && !running) { @@ -264,36 +321,37 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { } } - if (mEnableCallback != null) { - try { - mEnableCallback.onEnableResult(res ? - BluetoothDevice.RESULT_SUCCESS : - BluetoothDevice.RESULT_FAILURE); - } catch (RemoteException e) {} - } if (res) { - mIsEnabled = true; if (mSaveSetting) { persistBluetoothOnSetting(true); } mIsDiscovering = false; mBondState.loadBondState(); - mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS), + 3000); - // Update mode - mEventLoop.onModeChanged(getModeNative()); + // Log bluetooth on to battery stats. + long ident = Binder.clearCallingIdentity(); + try { + mBatteryStats.noteBluetoothOn(); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(ident); + } } - Intent intent = null; + + mEnableThread = null; + + setBluetoothState(res ? + BluetoothDevice.BLUETOOTH_STATE_ON : + BluetoothDevice.BLUETOOTH_STATE_OFF); + if (res) { - intent = new Intent(BluetoothIntent.ENABLED_ACTION); - } else { - intent = new Intent(BluetoothIntent.DISABLED_ACTION); + // Update mode + mEventLoop.onModeChanged(getModeNative()); } - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent, BLUETOOTH_PERM); - mEnableThread = null; } } @@ -320,18 +378,24 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { private final HashMap<String, Integer> mState = new HashMap<String, Integer>(); private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>(); private final ArrayList<String> mAutoPairingFailures = new ArrayList<String>(); - // List of all the vendor_id prefix of Bluetooth addresses for which - // auto pairing is not attempted + // List of all the vendor_id prefix of Bluetooth addresses for + // which auto pairing is not attempted. + // The following companies are included in the list below: + // ALPS (lexus), Murata (Prius 2007, Nokia 616), TEMIC SDS (Porsche, Audi), + // Parrot, Zhongshan General K-mate Electronics, Great Well + // Electronics, Flaircomm Electronics, Jatty Electronics, Delphi, + // Clarion, Novero, Denso (Lexus, Toyota), Johnson Controls (Acura), private final ArrayList<String> mAutoPairingBlacklisted = new ArrayList<String>(Arrays.asList( - "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", //ALPS - "00:21:4F", "00:23:06", "00:24:33", "00:A0:79", // ALPS - "00:0E:6D", "00:13:E0", "00:21:E8", "00:60:57",// Murata for Prius 2007 - "00:0E:9F" // TEMIC SDS for Porsche + "00:02:C7", "00:16:FE", "00:19:C1", "00:1B:FB", "00:1E:3D", "00:21:4F", + "00:23:06", "00:24:33", "00:A0:79", "00:0E:6D", "00:13:E0", "00:21:E8", + "00:60:57", "00:0E:9F", "00:12:1C", "00:18:91", "00:18:96", "00:13:04", + "00:16:FD", "00:22:A0", "00:0B:4C", "00:60:6F", "00:23:3D", "00:C0:59", + "00:0A:30" )); public synchronized void loadBondState() { - if (!mIsEnabled) { + if (mBluetoothState != BluetoothDevice.BLUETOOTH_STATE_TURNING_ON) { return; } String[] bonds = listBondingsNative(); @@ -1051,7 +1115,7 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { // If bluetooth is currently expected to be on, then enable or disable bluetooth if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) { if (enabled) { - enable(null, false); + enable(false); } else { disable(false); } @@ -1079,52 +1143,63 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - if (mIsEnabled) { - pw.println("\nBluetooth ENABLED: " + getAddress() + " (" + getName() + ")"); - pw.println("\nisDiscovering() = " + isDiscovering()); - - BluetoothHeadset headset = new BluetoothHeadset(mContext, null); - - String[] addresses = listRemoteDevices(); - - pw.println("\n--Known devices--"); - for (String address : addresses) { - pw.printf("%s %10s (%d) %s\n", address, - toBondStateString(mBondState.getBondState(address)), - mBondState.getAttempt(address), - getRemoteName(address)); - } - - addresses = listAclConnections(); - pw.println("\n--ACL connected devices--"); - for (String address : addresses) { - pw.println(address); - } - - // Rather not do this from here, but no-where else and I need this - // dump - pw.println("\n--Headset Service--"); - switch (headset.getState()) { - case BluetoothHeadset.STATE_DISCONNECTED: - pw.println("getState() = STATE_DISCONNECTED"); - break; - case BluetoothHeadset.STATE_CONNECTING: - pw.println("getState() = STATE_CONNECTING"); - break; - case BluetoothHeadset.STATE_CONNECTED: - pw.println("getState() = STATE_CONNECTED"); - break; - case BluetoothHeadset.STATE_ERROR: - pw.println("getState() = STATE_ERROR"); - break; - } - pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress()); - headset.close(); - - } else { - pw.println("\nBluetooth DISABLED"); - } - pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive); + pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive + "\n"); + + switch(mBluetoothState) { + case BluetoothDevice.BLUETOOTH_STATE_OFF: + pw.println("\nBluetooth OFF\n"); + return; + case BluetoothDevice.BLUETOOTH_STATE_TURNING_ON: + pw.println("\nBluetooth TURNING ON\n"); + return; + case BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF: + pw.println("\nBluetooth TURNING OFF\n"); + return; + case BluetoothDevice.BLUETOOTH_STATE_ON: + pw.println("\nBluetooth ON\n"); + } + + pw.println("\nLocal address = " + getAddress()); + pw.println("\nLocal name = " + getName()); + pw.println("\nisDiscovering() = " + isDiscovering()); + + BluetoothHeadset headset = new BluetoothHeadset(mContext, null); + + String[] addresses = listRemoteDevices(); + + pw.println("\n--Known devices--"); + for (String address : addresses) { + pw.printf("%s %10s (%d) %s\n", address, + toBondStateString(mBondState.getBondState(address)), + mBondState.getAttempt(address), + getRemoteName(address)); + } + + addresses = listAclConnections(); + pw.println("\n--ACL connected devices--"); + for (String address : addresses) { + pw.println(address); + } + + // Rather not do this from here, but no-where else and I need this + // dump + pw.println("\n--Headset Service--"); + switch (headset.getState()) { + case BluetoothHeadset.STATE_DISCONNECTED: + pw.println("getState() = STATE_DISCONNECTED"); + break; + case BluetoothHeadset.STATE_CONNECTING: + pw.println("getState() = STATE_CONNECTING"); + break; + case BluetoothHeadset.STATE_CONNECTED: + pw.println("getState() = STATE_CONNECTED"); + break; + case BluetoothHeadset.STATE_ERROR: + pw.println("getState() = STATE_ERROR"); + break; + } + pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress()); + headset.close(); } /* package */ static int bluezStringToScanMode(String mode) { @@ -1159,3 +1234,4 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { Log.d(TAG, msg); } } + diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 8e77eed..6be8eb9 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -46,10 +46,10 @@ class BluetoothEventLoop { private Thread mThread; private boolean mStarted; private boolean mInterrupted; - private HashMap<String, Integer> mPasskeyAgentRequestData; - private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; - private BluetoothDeviceService mBluetoothService; - private Context mContext; + private final HashMap<String, Integer> mPasskeyAgentRequestData; + private final HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; + private final BluetoothDeviceService mBluetoothService; + private final Context mContext; private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; private static final int EVENT_RESTART_BLUETOOTH = 2; @@ -77,7 +77,7 @@ class BluetoothEventLoop { break; case EVENT_RESTART_BLUETOOTH: mBluetoothService.disable(); - mBluetoothService.enable(null); + mBluetoothService.enable(); break; } } @@ -309,6 +309,12 @@ class BluetoothEventLoop { address = address.toUpperCase(); mPasskeyAgentRequestData.put(address, new Integer(nativeData)); + if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) { + // shutdown path + mBluetoothService.cancelPin(address); + return; + } + if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDING) { // we initiated the bonding @@ -347,7 +353,7 @@ class BluetoothEventLoop { private boolean onAuthAgentAuthorize(String address, String service, String uuid) { boolean authorized = false; - if (service.endsWith("service_audio")) { + if (mBluetoothService.isEnabled() && service.endsWith("service_audio")) { BluetoothA2dp a2dp = new BluetoothA2dp(mContext); authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 8495714..6f0be3a 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -28,6 +28,8 @@ import org.xml.sax.XMLReader; import android.content.res.Resources; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; @@ -41,6 +43,7 @@ import android.text.style.SuperscriptSpan; import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; +import android.util.Log; import com.android.internal.util.XmlUtils; import java.io.IOException; @@ -137,11 +140,52 @@ public class Html { */ public static String toHtml(Spanned text) { StringBuilder out = new StringBuilder(); + withinHtml(out, text); + return out.toString(); + } + + private static void withinHtml(StringBuilder out, Spanned text) { int len = text.length(); int next; for (int i = 0; i < text.length(); i = next) { - next = text.nextSpanTransition(i, len, QuoteSpan.class); + next = text.nextSpanTransition(i, len, ParagraphStyle.class); + ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class); + if (style.length > 0) { + out.append("<div "); + } + for(int j = 0; j < style.length; j++) { + if (style[j] instanceof AlignmentSpan) { + out.append("align=\""); + Layout.Alignment align = + ((AlignmentSpan) style[j]).getAlignment(); + if (align == Layout.Alignment.ALIGN_CENTER) { + out.append("center"); + } else if (align == Layout.Alignment.ALIGN_OPPOSITE) { + out.append("right"); + } else { + out.append("left"); + } + out.append("\" "); + } + } + if (style.length > 0) { + out.append(">"); + } + + withinDiv(out, text, i, next); + + if (style.length > 0) { + out.append("</div>"); + } + } + } + + private static void withinDiv(StringBuilder out, Spanned text, + int start, int end) { + int next; + for (int i = start; i < end; i = next) { + next = text.nextSpanTransition(i, end, QuoteSpan.class); QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class); for (QuoteSpan quote: quotes) { @@ -154,8 +198,6 @@ public class Html { out.append("</blockquote>\n"); } } - - return out.toString(); } private static void withinBlockquote(StringBuilder out, Spanned text, @@ -234,11 +276,32 @@ public class Html { // Don't output the dummy character underlying the image. i = next; } + if (style[j] instanceof AbsoluteSizeSpan) { + out.append("<font size =\""); + out.append(((AbsoluteSizeSpan) style[j]).getSize() / 6); + out.append("\">"); + } + if (style[j] instanceof ForegroundColorSpan) { + out.append("<font color =\"#"); + String color = Integer.toHexString(((ForegroundColorSpan) + style[j]).getForegroundColor() + 0x01000000); + while (color.length() < 6) { + color = "0" + color; + } + out.append(color); + out.append("\">"); + } } withinStyle(out, text, i, next); for (int j = style.length - 1; j >= 0; j--) { + if (style[j] instanceof ForegroundColorSpan) { + out.append("</font>"); + } + if (style[j] instanceof AbsoluteSizeSpan) { + out.append("</font>"); + } if (style[j] instanceof URLSpan) { out.append("</a>"); } diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 95acf9d..23e740d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -16,6 +16,8 @@ package android.text; +import android.emoji.EmojiFactory; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; @@ -36,6 +38,20 @@ import android.view.KeyEvent; * For text that will not change, use a {@link StaticLayout}. */ public abstract class Layout { + /* package */ static final EmojiFactory EMOJI_FACTORY = + EmojiFactory.newAvailableInstance(); + /* package */ static final int MIN_EMOJI, MAX_EMOJI; + + static { + if (EMOJI_FACTORY != null) { + MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua(); + MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua(); + } else { + MIN_EMOJI = -1; + MAX_EMOJI = -1; + } + }; + /** * Return how wide a layout would be necessary to display the * specified text with one line per paragraph. @@ -445,7 +461,9 @@ public abstract class Layout { public abstract int getParagraphDirection(int line); /** - * Returns whether the specified line contains one or more tabs. + * Returns whether the specified line contains one or more + * characters that need to be handled specially, like tabs + * or emoji. */ public abstract boolean getLineContainsTab(int line); @@ -1352,6 +1370,26 @@ public abstract class Layout { h = dir * nextTab(text, start, end, h * dir, parspans); segstart = j + 1; + } else if (hasTabs && buf[j] >= 0xD800 && buf[j] <= 0xDFFF && j + 1 < there) { + int emoji = Character.codePointAt(buf, j); + + if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { + Bitmap bm = EMOJI_FACTORY. + getBitmapFromAndroidPua(emoji); + + if (bm != null) { + h += Styled.drawText(canvas, text, + start + segstart, start + j, + dir, (i & 1) != 0, x + h, + top, y, bottom, paint, workPaint, + start + j != end); + + canvas.drawBitmap(bm, x + h, y - bm.getHeight(), paint); + h += bm.getWidth(); + j++; + segstart = j + 1; + } + } } } @@ -1394,7 +1432,22 @@ public abstract class Layout { int segstart = here; for (int j = hasTabs ? here : there; j <= there; j++) { - if (j == there || buf[j] == '\t') { + int codept = 0; + Bitmap bm = null; + + if (hasTabs && j < there) { + codept = buf[j]; + } + + if (codept >= 0xD800 && codept <= 0xDFFF && j + 1 < there) { + codept = Character.codePointAt(buf, j); + + if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) { + bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept); + } + } + + if (j == there || codept == '\t' || bm != null) { float segw; if (offset < start + j || @@ -1449,6 +1502,16 @@ public abstract class Layout { h = dir * nextTab(text, start, end, h * dir, tabs); } + if (bm != null) { + if (dir == DIR_RIGHT_TO_LEFT) { + h -= bm.getWidth(); + } else { + h += bm.getWidth(); + } + + j++; + } + segstart = j + 1; } } @@ -1488,7 +1551,22 @@ public abstract class Layout { } for (int i = hasTabs ? 0 : len; i <= len; i++) { - if (i == len || buf[i] == '\t') { + int codept = 0; + Bitmap bm = null; + + if (hasTabs && i < len) { + codept = buf[i]; + } + + if (codept >= 0xD800 && codept <= 0xDFFF && i < len) { + codept = Character.codePointAt(buf, i); + + if (codept >= MIN_EMOJI && codept <= MAX_EMOJI) { + bm = EMOJI_FACTORY.getBitmapFromAndroidPua(codept); + } + } + + if (i == len || codept == '\t' || bm != null) { workPaint.baselineShift = 0; h += Styled.measureText(paint, workPaint, text, @@ -1505,8 +1583,14 @@ public abstract class Layout { } } - if (i != len) - h = nextTab(text, start, end, h, tabs); + if (i != len) { + if (bm == null) { + h = nextTab(text, start, end, h, tabs); + } else { + h += bm.getWidth(); + i++; + } + } if (fm != null) { if (fm.ascent < ab) { @@ -1522,6 +1606,17 @@ public abstract class Layout { if (fm.bottom > bot) { bot = fm.bottom; } + + if (bm != null) { + int ht = -bm.getHeight(); + + if (ht < ab) { + ab = ht; + } + if (ht < top) { + top = ht; + } + } } here = i + 1; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 0fef40b..720d15a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -16,6 +16,7 @@ package android.text; +import android.graphics.Bitmap; import android.graphics.Paint; import com.android.internal.util.ArrayUtils; import android.util.Log; @@ -421,11 +422,16 @@ extends Layout // dump(chdirs, n, "final"); - // extra: enforce that all tabs go the primary direction + // extra: enforce that all tabs and surrogate characters go the + // primary direction + // TODO: actually do directions right for surrogates for (int j = 0; j < n; j++) { - if (chs[j] == '\t') + char c = chs[j]; + + if (c == '\t' || (c >= 0xD800 && c <= 0xDFFF)) { chdirs[j] = SOR; + } } // extra: enforce that object replacements go to the @@ -548,16 +554,29 @@ extends Layout char c = chs[j - start]; float before = w; - switch (c) { - case '\n': - break; - - case '\t': + if (c == '\n') { + ; + } else if (c == '\t') { w = Layout.nextTab(sub, start, end, w, null); tab = true; - break; - - default: + } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < next) { + int emoji = Character.codePointAt(chs, j - start); + + if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) { + Bitmap bm = EMOJI_FACTORY. + getBitmapFromAndroidPua(emoji); + + if (bm != null) { + w += bm.getWidth(); + tab = true; + j++; + } else { + w += widths[j - start + (end - start)]; + } + } else { + w += widths[j - start + (end - start)]; + } + } else { w += widths[j - start + (end - start)]; } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index f7ac522..23f3e3c 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -201,8 +201,6 @@ public class GestureDetector { private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout(); - // TODO make new double-tap timeout, and define its events (i.e. either time - // between down-down or time between up-down) private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); // constants for Message.what used by GestureHandler below diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a856b24..15e7eb2 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -62,7 +62,8 @@ interface IWindowManager void addAppToken(int addPos, IApplicationToken token, int groupId, int requestedOrientation, boolean fullscreen); void setAppGroupId(IBinder token, int groupId); - Configuration updateOrientationFromAppTokens(IBinder freezeThisOneIfNeeded); + Configuration updateOrientationFromAppTokens(in Configuration currentConfig, + IBinder freezeThisOneIfNeeded); void setAppOrientation(IApplicationToken token, int requestedOrientation); int getAppOrientation(IApplicationToken token); void setFocusedApp(IBinder token, boolean moveFocusNow); diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 430cc71..41779ba 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -235,6 +235,22 @@ public class KeyEvent implements Parcelable { public static final int FLAG_KEEP_TOUCH_MODE = 0x4; /** + * This mask is set if an event was known to come from a trusted part + * of the system. That is, the event is known to come from the user, + * and could not have been spoofed by a third party component. + */ + public static final int FLAG_FROM_SYSTEM = 0x8; + + /** + * This mask is used for compatibility, to identify enter keys that are + * coming from an IME whose enter key has been auto-labelled "next" or + * "done". This allows TextView to dispatch these as normal enter keys + * for old applications, but still do the appropriate action when + * receiving them. + */ + public static final int FLAG_EDITOR_ACTION = 0x10; + + /** * Returns the maximum keycode. */ public static int getMaxKeyCode() { @@ -440,6 +456,22 @@ public class KeyEvent implements Parcelable { } /** + * Make an exact copy of an existing key event. + */ + public KeyEvent(KeyEvent origEvent) { + mDownTime = origEvent.mDownTime; + mEventTime = origEvent.mEventTime; + mAction = origEvent.mAction; + mKeyCode = origEvent.mKeyCode; + mRepeatCount = origEvent.mRepeatCount; + mMetaState = origEvent.mMetaState; + mDeviceId = origEvent.mDeviceId; + mScancode = origEvent.mScancode; + mFlags = origEvent.mFlags; + mCharacters = origEvent.mCharacters; + } + + /** * Copy an existing key event, modifying its time and repeat count. * * @param origEvent The existing event to be copied. @@ -461,12 +493,26 @@ public class KeyEvent implements Parcelable { } /** + * Create a new key event that is the same as the given one, but whose + * event time and repeat count are replaced with the given value. + * + * @param event The existing event to be copied. This is not modified. + * @param eventTime The new event time + * (in {@link android.os.SystemClock#uptimeMillis}) of the event. + * @param newRepeat The new repeat count of the event. + */ + public static KeyEvent changeTimeRepeat(KeyEvent event, long eventTime, + int newRepeat) { + return new KeyEvent(event, eventTime, newRepeat); + } + + /** * Copy an existing key event, modifying its action. * * @param origEvent The existing event to be copied. * @param action The new action code of the event. */ - public KeyEvent(KeyEvent origEvent, int action) { + private KeyEvent(KeyEvent origEvent, int action) { mDownTime = origEvent.mDownTime; mEventTime = origEvent.mEventTime; mAction = action; @@ -481,6 +527,30 @@ public class KeyEvent implements Parcelable { } /** + * Create a new key event that is the same as the given one, but whose + * action is replaced with the given value. + * + * @param event The existing event to be copied. This is not modified. + * @param action The new action code of the event. + */ + public static KeyEvent changeAction(KeyEvent event, int action) { + return new KeyEvent(event, action); + } + + /** + * Create a new key event that is the same as the given one, but whose + * flags are replaced with the given value. + * + * @param event The existing event to be copied. This is not modified. + * @param flags The new flags constant. + */ + public static KeyEvent changeFlags(KeyEvent event, int flags) { + event = new KeyEvent(event); + event.mFlags = flags; + return event; + } + + /** * Don't use in new code, instead explicitly check * {@link #getAction()}. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c3e00c4..04447ca 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3409,6 +3409,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (imm != null && (mPrivateFlags & FOCUSED) != 0) { imm.focusOut(this); } + if (mPendingCheckForLongPress != null) { + removeCallbacks(mPendingCheckForLongPress); + } } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) { imm.focusIn(this); } @@ -7656,7 +7659,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { private int mOriginalWindowAttachCount; public void run() { - if (isPressed() && (mParent != null) && hasWindowFocus() + if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick()) { mHasPerformedLongPress = true; diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 6ea7a82..f604bc5 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -19,6 +19,7 @@ package android.view; import android.util.Log; import android.util.DisplayMetrics; import android.content.res.Resources; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.os.Environment; @@ -800,7 +801,7 @@ public class ViewDebug { View view = root.getRootView(); if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup) view; - dumpViewHierarchyWithProperties(group, out, 0); + dumpViewHierarchyWithProperties(group.getContext(), group, out, 0); } out.write("DONE."); out.newLine(); @@ -838,9 +839,9 @@ public class ViewDebug { return view.getClass().getName().equals(className) && view.hashCode() == hashCode; } - private static void dumpViewHierarchyWithProperties(ViewGroup group, + private static void dumpViewHierarchyWithProperties(Context context, ViewGroup group, BufferedWriter out, int level) { - if (!dumpViewWithProperties(group, out, level)) { + if (!dumpViewWithProperties(context, group, out, level)) { return; } @@ -848,14 +849,16 @@ public class ViewDebug { for (int i = 0; i < count; i++) { final View view = group.getChildAt(i); if (view instanceof ViewGroup) { - dumpViewHierarchyWithProperties((ViewGroup) view, out, level + 1); + dumpViewHierarchyWithProperties(context, (ViewGroup) view, out, level + 1); } else { - dumpViewWithProperties(view, out, level + 1); + dumpViewWithProperties(context, view, out, level + 1); } } } - private static boolean dumpViewWithProperties(View view, BufferedWriter out, int level) { + private static boolean dumpViewWithProperties(Context context, View view, + BufferedWriter out, int level) { + try { for (int i = 0; i < level; i++) { out.write(' '); @@ -864,7 +867,7 @@ public class ViewDebug { out.write('@'); out.write(Integer.toHexString(view.hashCode())); out.write(' '); - dumpViewProperties(view, out); + dumpViewProperties(context, view, out); out.newLine(); } catch (IOException e) { Log.w("View", "Error while dumping hierarchy tree"); @@ -945,23 +948,26 @@ public class ViewDebug { return methods; } - private static void dumpViewProperties(Object view, BufferedWriter out) throws IOException { - dumpViewProperties(view, out, ""); + private static void dumpViewProperties(Context context, Object view, + BufferedWriter out) throws IOException { + + dumpViewProperties(context, view, out, ""); } - private static void dumpViewProperties(Object view, BufferedWriter out, String prefix) - throws IOException { + private static void dumpViewProperties(Context context, Object view, + BufferedWriter out, String prefix) throws IOException { + Class<?> klass = view.getClass(); do { - exportFields(view, out, klass, prefix); - exportMethods(view, out, klass, prefix); + exportFields(context, view, out, klass, prefix); + exportMethods(context, view, out, klass, prefix); klass = klass.getSuperclass(); } while (klass != Object.class); } - private static void exportMethods(Object view, BufferedWriter out, Class<?> klass, - String prefix) throws IOException { + private static void exportMethods(Context context, Object view, BufferedWriter out, + Class<?> klass, String prefix) throws IOException { final Method[] methods = getExportedPropertyMethods(klass); @@ -976,9 +982,9 @@ public class ViewDebug { if (returnType == int.class) { final ExportedProperty property = sAnnotations.get(method); - if (property.resolveId() && view instanceof View) { + if (property.resolveId() && context != null) { final int id = (Integer) methodValue; - methodValue = resolveId(view, id); + methodValue = resolveId(context, id); } else { final IntToString[] mapping = property.mapping(); if (mapping.length > 0) { @@ -1005,11 +1011,11 @@ public class ViewDebug { final String valuePrefix = prefix + method.getName() + '_'; final String suffix = "()"; - exportUnrolledArray(view, out, property, array, valuePrefix, suffix); + exportUnrolledArray(context, out, property, array, valuePrefix, suffix); } else if (!returnType.isPrimitive()) { final ExportedProperty property = sAnnotations.get(method); if (property.deepExport()) { - dumpViewProperties(methodValue, out, prefix + property.prefix()); + dumpViewProperties(context, methodValue, out, prefix + property.prefix()); continue; } } @@ -1021,8 +1027,9 @@ public class ViewDebug { } } - private static void exportFields(Object view, BufferedWriter out, Class<?> klass, String prefix) - throws IOException { + private static void exportFields(Context context, Object view, BufferedWriter out, + Class<?> klass, String prefix) throws IOException { + final Field[] fields = getExportedPropertyFields(klass); int count = fields.length; @@ -1036,9 +1043,9 @@ public class ViewDebug { if (type == int.class) { final ExportedProperty property = sAnnotations.get(field); - if (property.resolveId() && view instanceof View) { + if (property.resolveId() && context != null) { final int id = field.getInt(view); - fieldValue = resolveId(view, id); + fieldValue = resolveId(context, id); } else { final IntToString[] mapping = property.mapping(); if (mapping.length > 0) { @@ -1063,14 +1070,15 @@ public class ViewDebug { final String valuePrefix = prefix + field.getName() + '_'; final String suffix = ""; - exportUnrolledArray(view, out, property, array, valuePrefix, suffix); + exportUnrolledArray(context, out, property, array, valuePrefix, suffix); // We exit here! return; } else if (!type.isPrimitive()) { final ExportedProperty property = sAnnotations.get(field); if (property.deepExport()) { - dumpViewProperties(field.get(view), out, prefix + property.prefix()); + dumpViewProperties(context, field.get(view), out, + prefix + property.prefix()); continue; } } @@ -1096,7 +1104,7 @@ public class ViewDebug { out.write(' '); } - private static void exportUnrolledArray(Object view, BufferedWriter out, + private static void exportUnrolledArray(Context context, BufferedWriter out, ExportedProperty property, int[] array, String prefix, String suffix) throws IOException { @@ -1106,7 +1114,7 @@ public class ViewDebug { final IntToString[] mapping = property.mapping(); final boolean hasMapping = mapping.length > 0; - final boolean resolveId = property.resolveId() && view instanceof View; + final boolean resolveId = property.resolveId() && context != null; final int valuesCount = array.length; for (int j = 0; j < valuesCount; j++) { @@ -1140,16 +1148,16 @@ public class ViewDebug { } if (resolveId) { - value = (String) resolveId(view, intValue); + value = (String) resolveId(context, intValue); } writeEntry(out, prefix, name, suffix, value); } } - private static Object resolveId(Object view, int id) { + private static Object resolveId(Context context, int id) { Object fieldValue; - final Resources resources = ((View) view).getContext().getResources(); + final Resources resources = context.getResources(); if (id >= 0) { try { fieldValue = resources.getResourceTypeName(id) + '/' + diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index dd2b154..fbb4d42 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -470,11 +470,20 @@ public final class ViewRoot extends Handler implements ViewParent, void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { synchronized (this) { + int oldSoftInputMode = mWindowAttributes.softInputMode; mWindowAttributes.copyFrom(attrs); if (newView) { mSoftInputMode = attrs.softInputMode; requestLayout(); } + // Don't lose the mode we last auto-computed. + if ((attrs.softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { + mWindowAttributes.softInputMode = (mWindowAttributes.softInputMode + & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + | (oldSoftInputMode + & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST); + } mWindowAttributesChanged = true; scheduleTraversals(); } @@ -1485,7 +1494,7 @@ public final class ViewRoot extends Handler implements ViewParent, + msg.obj + " to " + mView); deliverKeyEvent((KeyEvent)msg.obj, true); break; - case DISPATCH_POINTER: + case DISPATCH_POINTER: { MotionEvent event = (MotionEvent)msg.obj; boolean didFinish; @@ -1571,7 +1580,7 @@ public final class ViewRoot extends Handler implements ViewParent, // Let the exception fall through -- the looper will catch // it and take care of the bad app for us. } - break; + } break; case DISPATCH_TRACKBALL: deliverTrackballEvent((MotionEvent)msg.obj); break; @@ -1657,12 +1666,19 @@ public final class ViewRoot extends Handler implements ViewParent, case DIE: dispatchDetachedFromWindow(); break; - case DISPATCH_KEY_FROM_IME: + case DISPATCH_KEY_FROM_IME: { if (LOCAL_LOGV) Log.v( "ViewRoot", "Dispatching key " + msg.obj + " from IME to " + mView); + KeyEvent event = (KeyEvent)msg.obj; + if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) { + // The IME is trying to say this event is from the + // system! Bad bad bad! + event = KeyEvent.changeFlags(event, + event.getFlags()&~KeyEvent.FLAG_FROM_SYSTEM); + } deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); - break; + } break; case FINISH_INPUT_CONNECTION: { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 47b52e4..4230afa 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -18,7 +18,7 @@ package android.view; import android.graphics.Rect; -import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArrayList; /** * A view tree observer is used to register listeners that can be notified of global @@ -30,12 +30,12 @@ import java.util.ArrayList; * for more information. */ public final class ViewTreeObserver { - private ArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; - private ArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; - private ArrayList<OnPreDrawListener> mOnPreDrawListeners; - private ArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; - private ArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; - private ArrayList<OnScrollChangedListener> mOnScrollChangedListeners; + private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners; + private CopyOnWriteArrayList<OnGlobalLayoutListener> mOnGlobalLayoutListeners; + private CopyOnWriteArrayList<OnPreDrawListener> mOnPreDrawListeners; + private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners; + private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; + private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; private boolean mAlive = true; @@ -283,7 +283,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalFocusListeners == null) { - mOnGlobalFocusListeners = new ArrayList<OnGlobalFocusChangeListener>(); + mOnGlobalFocusListeners = new CopyOnWriteArrayList<OnGlobalFocusChangeListener>(); } mOnGlobalFocusListeners.add(listener); @@ -318,7 +318,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnGlobalLayoutListeners == null) { - mOnGlobalLayoutListeners = new ArrayList<OnGlobalLayoutListener>(); + mOnGlobalLayoutListeners = new CopyOnWriteArrayList<OnGlobalLayoutListener>(); } mOnGlobalLayoutListeners.add(listener); @@ -352,7 +352,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnPreDrawListeners == null) { - mOnPreDrawListeners = new ArrayList<OnPreDrawListener>(); + mOnPreDrawListeners = new CopyOnWriteArrayList<OnPreDrawListener>(); } mOnPreDrawListeners.add(listener); @@ -388,7 +388,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnScrollChangedListeners == null) { - mOnScrollChangedListeners = new ArrayList<OnScrollChangedListener>(); + mOnScrollChangedListeners = new CopyOnWriteArrayList<OnScrollChangedListener>(); } mOnScrollChangedListeners.add(listener); @@ -424,7 +424,7 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnTouchModeChangeListeners == null) { - mOnTouchModeChangeListeners = new ArrayList<OnTouchModeChangeListener>(); + mOnTouchModeChangeListeners = new CopyOnWriteArrayList<OnTouchModeChangeListener>(); } mOnTouchModeChangeListeners.add(listener); @@ -460,7 +460,8 @@ public final class ViewTreeObserver { checkIsAlive(); if (mOnComputeInternalInsetsListeners == null) { - mOnComputeInternalInsetsListeners = new ArrayList<OnComputeInternalInsetsListener>(); + mOnComputeInternalInsetsListeners = + new CopyOnWriteArrayList<OnComputeInternalInsetsListener>(); } mOnComputeInternalInsetsListeners.add(listener); @@ -518,11 +519,14 @@ public final class ViewTreeObserver { * Notifies registered listeners that focus has changed. */ final void dispatchOnGlobalFocusChange(View oldFocus, View newFocus) { - final ArrayList<OnGlobalFocusChangeListener> globaFocusListeners = mOnGlobalFocusListeners; - if (globaFocusListeners != null) { - final int count = globaFocusListeners.size(); - for (int i = count - 1; i >= 0; i--) { - globaFocusListeners.get(i).onGlobalFocusChanged(oldFocus, newFocus); + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnGlobalFocusChangeListener> listeners = mOnGlobalFocusListeners; + if (listeners != null) { + for (OnGlobalFocusChangeListener listener : listeners) { + listener.onGlobalFocusChanged(oldFocus, newFocus); } } } @@ -533,11 +537,14 @@ public final class ViewTreeObserver { * not attached to a Window or in the GONE state. */ public final void dispatchOnGlobalLayout() { - final ArrayList<OnGlobalLayoutListener> globaLayoutListeners = mOnGlobalLayoutListeners; - if (globaLayoutListeners != null) { - final int count = globaLayoutListeners.size(); - for (int i = count - 1; i >= 0; i--) { - globaLayoutListeners.get(i).onGlobalLayout(); + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners; + if (listeners != null) { + for (OnGlobalLayoutListener listener : listeners) { + listener.onGlobalLayout(); } } } @@ -551,12 +558,15 @@ public final class ViewTreeObserver { * @return True if the current draw should be canceled and resceduled, false otherwise. */ public final boolean dispatchOnPreDraw() { + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. boolean cancelDraw = false; - final ArrayList<OnPreDrawListener> preDrawListeners = mOnPreDrawListeners; - if (preDrawListeners != null) { - final int count = preDrawListeners.size(); - for (int i = count - 1; i >= 0; i--) { - cancelDraw |= !preDrawListeners.get(i).onPreDraw(); + final CopyOnWriteArrayList<OnPreDrawListener> listeners = mOnPreDrawListeners; + if (listeners != null) { + for (OnPreDrawListener listener : listeners) { + cancelDraw |= !listener.onPreDraw(); } } return cancelDraw; @@ -568,11 +578,15 @@ public final class ViewTreeObserver { * @param inTouchMode True if the touch mode is now enabled, false otherwise. */ final void dispatchOnTouchModeChanged(boolean inTouchMode) { - final ArrayList<OnTouchModeChangeListener> touchModeListeners = mOnTouchModeChangeListeners; - if (touchModeListeners != null) { - final int count = touchModeListeners.size(); - for (int i = count - 1; i >= 0; i--) { - touchModeListeners.get(i).onTouchModeChanged(inTouchMode); + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnTouchModeChangeListener> listeners = + mOnTouchModeChangeListeners; + if (listeners != null) { + for (OnTouchModeChangeListener listener : listeners) { + listener.onTouchModeChanged(inTouchMode); } } } @@ -581,11 +595,14 @@ public final class ViewTreeObserver { * Notifies registered listeners that something has scrolled. */ final void dispatchOnScrollChanged() { - final ArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; - + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnScrollChangedListener> listeners = mOnScrollChangedListeners; if (listeners != null) { - for (OnScrollChangedListener scl : mOnScrollChangedListeners) { - scl.onScrollChanged(); + for (OnScrollChangedListener listener : listeners) { + listener.onScrollChanged(); } } } @@ -594,7 +611,8 @@ public final class ViewTreeObserver { * Returns whether there are listeners for computing internal insets. */ final boolean hasComputeInternalInsetsListeners() { - final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; + final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + mOnComputeInternalInsetsListeners; return (listeners != null && listeners.size() > 0); } @@ -602,11 +620,15 @@ public final class ViewTreeObserver { * Calls all listeners to compute the current insets. */ final void dispatchOnComputeInternalInsets(InternalInsetsInfo inoutInfo) { - final ArrayList<OnComputeInternalInsetsListener> listeners = mOnComputeInternalInsetsListeners; + // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to + // perform the dispatching. The iterator is a safe guard against listeners that + // could mutate the list by calling the various add/remove methods. This prevents + // the array from being modified while we iterate it. + final CopyOnWriteArrayList<OnComputeInternalInsetsListener> listeners = + mOnComputeInternalInsetsListeners; if (listeners != null) { - final int count = listeners.size(); - for (int i = count - 1; i >= 0; i--) { - listeners.get(i).onComputeInternalInsets(inoutInfo); + for (OnComputeInternalInsetsListener listener : listeners) { + listener.onComputeInternalInsets(inoutInfo); } } } diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index b00e565..c718bac 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -78,12 +78,37 @@ public class EditorInfo implements InputType, Parcelable { public static final int IME_ACTION_DONE = 0x00000006; /** + * Flag of {@link #imeOptions}: used to specify that the IME does not need + * to show its extracted text UI. For input methods that may be fullscreen, + * often when in landscape mode, this allows them to be smaller and let part + * of the application be shown behind. Though there will likely be limited + * access to the application available from the user, it can make the + * experience of a (mostly) fullscreen IME less jarring. Note that when + * this flag is specified the IME may <em>not</em> be set up to be able + * to display text, so it should only be used in situations where this is + * not needed. + */ + public static final int IME_FLAG_NO_EXTRACT_UI = 0x10000000; + + /** + * Flag of {@link #imeOptions}: used in conjunction with + * {@link #IME_MASK_ACTION}, this indicates that the action should not + * be available as an accessory button when the input method is full-screen. + * Note that by setting this flag, there can be cases where the action + * is simply never available to the user. Setting this generally means + * that you think showing text being edited is more important than the + * action you have supplied. + */ + public static final int IME_FLAG_NO_ACCESSORY_ACTION = 0x20000000; + + /** * Flag of {@link #imeOptions}: used in conjunction with * {@link #IME_MASK_ACTION}, this indicates that the action should not - * be available in-line as the same as a "enter" key. Typically this is + * be available in-line as a replacement for "enter" key. Typically this is * because the action has such a significant impact or is not recoverable * enough that accidentally hitting it should be avoided, such as sending - * a message. + * a message. Note that {@link android.widget.TextView} will automatically set this + * flag for you on multi-line text views. */ public static final int IME_FLAG_NO_ENTER_ACTION = 0x40000000; diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index dcf68cd..4528b73 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -282,9 +282,7 @@ public final class CacheManager { CacheResult result = mDataBase.getCache(url); if (result != null) { if (result.contentLength == 0) { - if (result.httpStatusCode != 301 - && result.httpStatusCode != 302 - && result.httpStatusCode != 307) { + if (!checkCacheRedirect(result.httpStatusCode)) { // this should not happen. If it does, remove it. mDataBase.removeCache(url); return null; @@ -350,6 +348,17 @@ public final class CacheManager { return null; } + // according to the rfc 2616, the 303 response MUST NOT be cached. + if (statusCode == 303) { + return null; + } + + // like the other browsers, do not cache redirects containing a cookie + // header. + if (checkCacheRedirect(statusCode) && !headers.getSetCookie().isEmpty()) { + return null; + } + CacheResult ret = parseHeaders(statusCode, headers, mimeType); if (ret != null) { setupFiles(url, ret); @@ -395,9 +404,7 @@ public final class CacheManager { } cacheRet.contentLength = cacheRet.outFile.length(); - if (cacheRet.httpStatusCode == 301 - || cacheRet.httpStatusCode == 302 - || cacheRet.httpStatusCode == 307) { + if (checkCacheRedirect(cacheRet.httpStatusCode)) { // location is in database, no need to keep the file cacheRet.contentLength = 0; cacheRet.localPath = new String(); @@ -471,6 +478,15 @@ public final class CacheManager { } } + private static boolean checkCacheRedirect(int statusCode) { + if (statusCode == 301 || statusCode == 302 || statusCode == 307) { + // as 303 can't be cached, we do not return true + return true; + } else { + return false; + } + } + @SuppressWarnings("deprecation") private static void setupFiles(String url, CacheResult cacheRet) { if (true) { diff --git a/core/java/android/webkit/HttpAuthHandler.java b/core/java/android/webkit/HttpAuthHandler.java index 48b9eec..84dc9f0 100644 --- a/core/java/android/webkit/HttpAuthHandler.java +++ b/core/java/android/webkit/HttpAuthHandler.java @@ -80,8 +80,7 @@ public class HttpAuthHandler extends Handler { break; case AUTH_CANCEL: - - mNetwork.resetHandlersAndStopLoading(loader.getFrame()); + loader.handleAuthResponse(null, null); break; } @@ -126,24 +125,6 @@ public class HttpAuthHandler extends Handler { } /** - * Resets the HTTP-authentication request handler, removes - * all loaders that share the same BrowserFrame - * - * @param frame The browser frame - */ - /* package */ void reset(BrowserFrame frame) { - synchronized (mLoaderQueue) { - ListIterator<LoadListener> i = mLoaderQueue.listIterator(0); - while (i.hasNext()) { - LoadListener loader = i.next(); - if (frame == loader.getFrame()) { - i.remove(); - } - } - } - } - - /** * Enqueues the loader, if the loader is the only element * in the queue, starts processing the loader * diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index f9fb0b0..c64200c 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -377,10 +377,6 @@ class LoadListener extends Handler implements EventHandler { } } - // if there is buffered data, commit them in the end - boolean needToCommit = mAuthHeader != null && !mustAuthenticate - && mNativeLoader != 0 && !mDataBuilder.isEmpty(); - // it is only here that we can reset the last mAuthHeader object // (if existed) and start a new one!!! mAuthHeader = null; @@ -415,10 +411,6 @@ class LoadListener extends Handler implements EventHandler { } } commitHeadersCheckRedirect(); - - if (needToCommit) { - commitLoad(); - } } /** @@ -452,6 +444,8 @@ class LoadListener extends Handler implements EventHandler { status.put("minor", minorVersion); status.put("code", code); status.put("reason", reasonPhrase); + // New status means new data. Clear the old. + mDataBuilder.clear(); sendMessageInternal(obtainMessage(MSG_STATUS, status)); } @@ -613,7 +607,6 @@ class LoadListener extends Handler implements EventHandler { // ask for it, so make sure we have a valid CacheLoader // before calling it. if (mCacheLoader != null) { - detachRequestHandle(); mCacheLoader.load(); if (Config.LOGV) { Log.v(LOGTAG, "LoadListener cache load url=" + url()); @@ -738,10 +731,16 @@ class LoadListener extends Handler implements EventHandler { if (mRequestHandle != null) { mRequestHandle.handleSslErrorResponse(proceed); } + if (!proceed) { + // Commit whatever data we have and tear down the loader. + commitLoad(); + tearDown(); + } } /** - * Uses user-supplied credentials to restar a request. + * Uses user-supplied credentials to restart a request. If the credentials + * are null, cancel the request. */ void handleAuthResponse(String username, String password) { if (Config.LOGV) { @@ -780,6 +779,10 @@ class LoadListener extends Handler implements EventHandler { } } } + } else { + // Commit whatever data we have and tear down the loader. + commitLoad(); + tearDown(); } } @@ -944,13 +947,12 @@ class LoadListener extends Handler implements EventHandler { * @return native response pointer */ private int createNativeResponse() { - // The reason we change HTTP_NOT_MODIFIED to HTTP_OK is because we know - // that WebCore never sends the if-modified-since header. Our - // CacheManager does it for us. If the server responds with a 304, then - // we treat it like it was a 200 code and proceed with loading the file - // from the cache. - int statusCode = mStatusCode == HTTP_NOT_MODIFIED - ? HTTP_OK : mStatusCode; + // If WebCore sends if-modified-since, mCacheLoader is null. If + // CacheManager sends it, mCacheLoader is not null. In this case, if the + // server responds with a 304, then we treat it like it was a 200 code + // and proceed with loading the file from the cache. + int statusCode = (mStatusCode == HTTP_NOT_MODIFIED && + mCacheLoader != null) ? HTTP_OK : mStatusCode; // pass content-type content-length and content-encoding final int nativeResponse = nativeCreateResponse( mUrl, statusCode, mStatusText, @@ -1181,8 +1183,6 @@ class LoadListener extends Handler implements EventHandler { // sync. Add 1 to account for the current redirect. mCacheRedirectCount = mRequestHandle.getRedirectCount() + 1; } - // Clear the buffered data since the redirect is valid. - mDataBuilder.clear(); } else { commitHeaders(); commitLoad(); @@ -1197,9 +1197,10 @@ class LoadListener extends Handler implements EventHandler { /** * Parses the content-type header. + * The first part only allows '-' if it follows x or X. */ private static final Pattern CONTENT_TYPE_PATTERN = - Pattern.compile("^([a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); + Pattern.compile("^((?:[xX]-)?[a-zA-Z\\*]+/[\\w\\+\\*-]+[\\.[\\w\\+-]+]*)$"); private void parseContentTypeHeader(String contentType) { if (Config.LOGV) { diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index c9cc208..85c2275 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -496,6 +496,7 @@ public /* package */ class MimeTypeMap { sMimeTypeMap.loadEntry("video/x-msvideo", "avi", false); sMimeTypeMap.loadEntry("video/x-sgi-movie", "movie", false); sMimeTypeMap.loadEntry("x-conference/x-cooltalk", "ice", false); + sMimeTypeMap.loadEntry("x-epoc/x-sisx-app", "sisx", false); } return sMimeTypeMap; diff --git a/core/java/android/webkit/Network.java b/core/java/android/webkit/Network.java index 74622b3..6fa0775 100644 --- a/core/java/android/webkit/Network.java +++ b/core/java/android/webkit/Network.java @@ -261,24 +261,6 @@ class Network { } /** - * If we need to stop loading done in a handler (here, browser frame), we - * send a message to the handler to stop loading, and remove all loaders - * that share the same CallbackProxy in question from all local - * handlers (such as ssl-error and http-authentication handler). - * @param proxy The CallbackProxy responsible for cancelling the current - * load. - */ - public void resetHandlersAndStopLoading(BrowserFrame frame) { - if (Config.LOGV) { - Log.v(LOGTAG, "Network.resetHandlersAndStopLoading()"); - } - - frame.stopLoading(); - mSslErrorHandler.reset(frame); - mHttpAuthHandler.reset(frame); - } - - /** * Saves the state of network handlers (user SSL and HTTP-authentication * preferences). * @param outState The out-state to save (write) to. diff --git a/core/java/android/webkit/SslErrorHandler.java b/core/java/android/webkit/SslErrorHandler.java index 115434a..2e2fa12 100644 --- a/core/java/android/webkit/SslErrorHandler.java +++ b/core/java/android/webkit/SslErrorHandler.java @@ -118,20 +118,6 @@ public class SslErrorHandler extends Handler { } /** - * Resets the SSL error handler, removes all loaders that - * share the same BrowserFrame. - */ - /* package */ synchronized void reset(BrowserFrame frame) { - ListIterator<LoadListener> i = mLoaderQueue.listIterator(0); - while (i.hasNext()) { - LoadListener loader = i.next(); - if (frame == loader.getFrame()) { - i.remove(); - } - } - } - - /** * Handles SSL error(s) on the way up to the user. */ /* package */ synchronized void handleSslErrorRequest(LoadListener loader) { @@ -244,12 +230,8 @@ public class SslErrorHandler extends Handler { primary > mSslPrefTable.getInt(host)) { mSslPrefTable.putInt(host, new Integer(primary)); } - - loader.handleSslErrorResponse(proceed); - } else { - loader.handleSslErrorResponse(proceed); - mNetwork.resetHandlersAndStopLoading(loader.getFrame()); } + loader.handleSslErrorResponse(proceed); } } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 65544d4..025e6bb 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -153,6 +153,7 @@ public class WebSettings { private boolean mNeedInitialFocus = true; private boolean mNavDump = false; private boolean mSupportZoom = true; + private boolean mBuiltInZoomControls = false; private boolean mAllowFileAccess = true; // Class to handle messages before WebCore is ready. @@ -364,6 +365,20 @@ public class WebSettings { } /** + * Sets whether the zoom mechanism built into WebView is used. + */ + public void setBuiltInZoomControls(boolean enabled) { + mBuiltInZoomControls = enabled; + } + + /** + * Returns true if the zoom mechanism built into WebView is being used. + */ + public boolean getBuiltInZoomControls() { + return mBuiltInZoomControls; + } + + /** * Enable or disable file access within WebView. File access is enabled by * default. */ diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 753267f..3205820 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -56,17 +56,20 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; +import android.view.animation.AlphaAnimation; import android.view.inputmethod.InputMethodManager; import android.webkit.TextDialog.AutoCompleteAdapter; import android.webkit.WebViewCore.EventHub; import android.widget.AbsoluteLayout; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.ListView; import android.widget.Scroller; import android.widget.Toast; import android.widget.ZoomButtonsController; +import android.widget.ZoomControls; import android.widget.AdapterView.OnItemClickListener; import java.io.File; @@ -85,6 +88,9 @@ import java.util.List; * It uses the WebKit rendering engine to display * web pages and includes methods to navigate forward and backward * through a history, zoom in and out, perform text searches and more.</p> + * <p>To enable the built-in zoom, set + * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} + * (introduced in API version 3). * <p>Note that, in order for your Activity to access the Internet and load web pages * in a WebView, you must add the <var>INTERNET</var> permissions to your * Android Manifest file:</p> @@ -106,6 +112,57 @@ public class WebView extends AbsoluteLayout static final boolean DEBUG = false; static final boolean LOGV_ENABLED = DEBUG ? Config.LOGD : Config.LOGV; + private class ExtendedZoomControls extends FrameLayout { + public ExtendedZoomControls(Context context, AttributeSet attrs) { + super(context, attrs); + LayoutInflater inflater = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true); + mZoomControls = (ZoomControls) findViewById(com.android.internal.R.id.zoomControls); + mZoomMagnify = (ImageView) findViewById(com.android.internal.R.id.zoomMagnify); + } + + public void show(boolean showZoom, boolean canZoomOut) { + mZoomControls.setVisibility(showZoom ? View.VISIBLE : View.GONE); + mZoomMagnify.setVisibility(canZoomOut ? View.VISIBLE : View.GONE); + fade(View.VISIBLE, 0.0f, 1.0f); + } + + public void hide() { + fade(View.GONE, 1.0f, 0.0f); + } + + private void fade(int visibility, float startAlpha, float endAlpha) { + AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); + anim.setDuration(500); + startAnimation(anim); + setVisibility(visibility); + } + + public void setIsZoomMagnifyEnabled(boolean isEnabled) { + mZoomMagnify.setEnabled(isEnabled); + } + + public boolean hasFocus() { + return mZoomControls.hasFocus() || mZoomMagnify.hasFocus(); + } + + public void setOnZoomInClickListener(OnClickListener listener) { + mZoomControls.setOnZoomInClickListener(listener); + } + + public void setOnZoomOutClickListener(OnClickListener listener) { + mZoomControls.setOnZoomOutClickListener(listener); + } + + public void setOnZoomMagnifyClickListener(OnClickListener listener) { + mZoomMagnify.setOnClickListener(listener); + } + + ZoomControls mZoomControls; + ImageView mZoomMagnify; + } + /** * Transportation object for returning WebView across thread boundaries. */ @@ -232,6 +289,9 @@ public class WebView extends AbsoluteLayout private static final int LONG_PRESS_TIMEOUT = 1000; // needed to avoid flinging after a pause of no movement private static final int MIN_FLING_TIME = 250; + // The time that the Zoom Controls are visible before fading away + private static final long ZOOM_CONTROLS_TIMEOUT = + ViewConfiguration.getZoomControlsTimeout(); // 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; @@ -472,6 +532,10 @@ public class WebView extends AbsoluteLayout } } + // The View containing the zoom controls + private ExtendedZoomControls mZoomControls; + private Runnable mZoomControlRunnable; + private ZoomButtonsController mZoomButtonsController; private ImageView mZoomOverviewButton; private ImageView mZoomFitPageButton; @@ -484,11 +548,6 @@ public class WebView extends AbsoluteLayout private ZoomButtonsController.OnZoomListener mZoomListener = new ZoomButtonsController.OnZoomListener() { - public void onCenter(int x, int y) { - // Don't translate when the control is invoked, hence we do nothing - // in this callback - } - public void onVisibilityChanged(boolean visible) { if (visible) { switchOutDrawHistory(); @@ -582,10 +641,26 @@ public class WebView extends AbsoluteLayout } private void updateZoomButtonsEnabled() { - mZoomButtonsController.setZoomInEnabled(mActualScale < mMaxZoomScale); - mZoomButtonsController.setZoomOutEnabled(mActualScale > mMinZoomScale); - mZoomFitPageButton.setEnabled(mActualScale != 1); - mZoomOverviewButton.setEnabled(canZoomScrollOut()); + boolean canZoomIn = mActualScale < mMaxZoomScale; + boolean canZoomOut = mActualScale > mMinZoomScale; + if (!canZoomIn && !canZoomOut) { + // Hide the zoom in and out buttons, as well as the fit to page + // button, if the page cannot zoom + mZoomButtonsController.getZoomControls().setVisibility(View.GONE); + mZoomFitPageButton.setVisibility(View.GONE); + } else { + // Bring back the hidden zoom controls. + mZoomButtonsController.getZoomControls() + .setVisibility(View.VISIBLE); + mZoomFitPageButton.setVisibility(View.VISIBLE); + // Set each one individually, as a page may be able to zoom in + // or out. + mZoomButtonsController.setZoomInEnabled(canZoomIn); + mZoomButtonsController.setZoomOutEnabled(canZoomOut); + mZoomFitPageButton.setEnabled(mActualScale != 1); + } + mZoomOverviewButton.setVisibility(canZoomScrollOut() ? View.VISIBLE: + View.GONE); } private void init() { @@ -1332,7 +1407,13 @@ public class WebView extends AbsoluteLayout return; } clearTextEntry(); - mZoomButtonsController.setVisible(true); + if (getSettings().getBuiltInZoomControls()) { + mZoomButtonsController.setVisible(true); + } else { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + } } /** @@ -2535,8 +2616,17 @@ public class WebView extends AbsoluteLayout private void startZoomScrollOut() { setHorizontalScrollBarEnabled(false); setVerticalScrollBarEnabled(false); - if (mZoomButtonsController.isVisible()) { - mZoomButtonsController.setVisible(false); + if (getSettings().getBuiltInZoomControls()) { + if (mZoomButtonsController.isVisible()) { + mZoomButtonsController.setVisible(false); + } + } else { + if (mZoomControlRunnable != null) { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + } + if (mZoomControls != null) { + mZoomControls.hide(); + } } int width = getViewWidth(); int height = getViewHeight(); @@ -3206,7 +3296,6 @@ public class WebView extends AbsoluteLayout // Clean up the zoom controller mZoomButtonsController.setVisible(false); - ZoomButtonsController.finishZoomTutorial(mContext, false); } // Implementation for OnHierarchyChangeListener @@ -3255,7 +3344,7 @@ public class WebView extends AbsoluteLayout // false for the first parameter } } else { - if (!mZoomButtonsController.isVisible()) { + if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) { /* * The zoom controls come in their own window, so our window * loses focus. Our policy is to not draw the focus ring if @@ -3527,7 +3616,9 @@ public class WebView extends AbsoluteLayout mWebViewCore .sendMessage(EventHub.SET_SNAP_ANCHOR, 0, 0); } - if (getSettings().supportZoom() + WebSettings settings = getSettings(); + if (settings.supportZoom() + && settings.getBuiltInZoomControls() && !mZoomButtonsController.isVisible() && (canZoomScrollOut() || mMinZoomScale < mMaxZoomScale)) { @@ -3594,6 +3685,21 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; mUserScroll = true; } + + if (!getSettings().getBuiltInZoomControls()) { + boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; + boolean showMagnify = canZoomScrollOut(); + if (mZoomControls != null && (showPlusMinus || showMagnify)) { + if (mZoomControls.getVisibility() == View.VISIBLE) { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + } else { + mZoomControls.show(showPlusMinus, showMagnify); + } + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + } + } + if (done) { // return false to indicate that we can't pan out of the // view space @@ -4050,18 +4156,83 @@ public class WebView extends AbsoluteLayout } } - // TODO: deprecate /** * Returns a view containing zoom controls i.e. +/- buttons. The caller is * in charge of installing this view to the view hierarchy. This view will * become visible when the user starts scrolling via touch and fade away if * the user does not interact with it. * <p/> - * From 1.5, WebView change to use ZoomButtonsController. This will return - * an invisible dummy view for backwards compatibility. + * API version 3 introduces a built-in zoom mechanism that is shown + * automatically by the MapView. This is the preferred approach for + * showing the zoom UI. + * + * @deprecated The built-in zoom mechanism is preferred, see + * {@link WebSettings#setBuiltInZoomControls(boolean)}. */ + @Deprecated public View getZoomControls() { - return mZoomButtonsController.getDummyZoomControls(); + if (!getSettings().supportZoom()) { + Log.w(LOGTAG, "This WebView doesn't support zoom."); + return null; + } + if (mZoomControls == null) { + mZoomControls = createZoomControls(); + + /* + * need to be set to VISIBLE first so that getMeasuredHeight() in + * {@link #onSizeChanged()} can return the measured value for proper + * layout. + */ + mZoomControls.setVisibility(View.VISIBLE); + mZoomControlRunnable = new Runnable() { + public void run() { + + /* Don't dismiss the controls if the user has + * focus on them. Wait and check again later. + */ + if (!mZoomControls.hasFocus()) { + mZoomControls.hide(); + } else { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + } + } + }; + } + return mZoomControls; + } + + private ExtendedZoomControls createZoomControls() { + ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext + , null); + zoomControls.setOnZoomInClickListener(new OnClickListener() { + public void onClick(View v) { + // reset time out + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + zoomIn(); + } + }); + zoomControls.setOnZoomOutClickListener(new OnClickListener() { + public void onClick(View v) { + // reset time out + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + zoomOut(); + } + }); + zoomControls.setOnZoomMagnifyClickListener(new OnClickListener() { + public void onClick(View v) { + mPrivateHandler.removeCallbacks(mZoomControlRunnable); + mPrivateHandler.postDelayed(mZoomControlRunnable, + ZOOM_CONTROLS_TIMEOUT); + zoomScrollOut(); + } + }); + return zoomControls; } /** @@ -4070,7 +4241,7 @@ public class WebView extends AbsoluteLayout * * @return The instance of {@link ZoomButtonsController} used by this class, * or null if it is unavailable. - * @hide pending API council + * @hide */ public ZoomButtonsController getZoomButtonsController() { return mZoomButtonsController; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index bd4bba8..965d900 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -905,10 +905,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te ((Filterable) getAdapter()).getFilter() == null) { return false; } - final Context context = mContext; - final InputMethodManager inputManager = (InputMethodManager) - context.getSystemService(Context.INPUT_METHOD_SERVICE); - return !inputManager.isFullscreenMode(); + return true; } /** @@ -2904,7 +2901,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te KeyEvent forwardEvent = event; if (forwardEvent.getRepeatCount() > 0) { - forwardEvent = new KeyEvent(event, event.getEventTime(), 0); + forwardEvent = KeyEvent.changeTimeRepeat(event, event.getEventTime(), 0); } int action = event.getAction(); @@ -2967,6 +2964,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // want to figure out why this is. mTextFilter.setRawInputType(EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_FILTER); + mTextFilter.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); mTextFilter.addTextChangedListener(this); p.setFocusable(false); p.setTouchable(false); diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 1d553f1..04cb8a0 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -164,7 +164,7 @@ public abstract class AbsSeekBar extends ProgressBar { void onProgressRefresh(float scale, boolean fromUser) { Drawable thumb = mThumb; if (thumb != null) { - setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE); + setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE); /* * Since we draw translated, the drawable's bounds that it signals * for invalidation won't be the actual bounds we want invalidated, @@ -189,7 +189,7 @@ public abstract class AbsSeekBar extends ProgressBar { if (thumbHeight > trackHeight) { if (thumb != null) { - setThumbPos(w, h, thumb, scale, 0); + setThumbPos(w, thumb, scale, 0); } int gapForCenteringTrack = (thumbHeight - trackHeight) / 2; if (d != null) { @@ -206,16 +206,15 @@ public abstract class AbsSeekBar extends ProgressBar { } int gap = (trackHeight - thumbHeight) / 2; if (thumb != null) { - setThumbPos(w, h, thumb, scale, gap); + setThumbPos(w, thumb, scale, gap); } } } /** * @param gap If set to {@link Integer#MIN_VALUE}, this will be ignored and - * the old vertical bounds will be used. */ - private void setThumbPos(int w, int h, Drawable thumb, float scale, int gap) { + private void setThumbPos(int w, Drawable thumb, float scale, int gap) { int available = w - mPaddingLeft - mPaddingRight; int thumbWidth = thumb.getIntrinsicWidth(); int thumbHeight = thumb.getIntrinsicHeight(); @@ -353,6 +352,12 @@ public abstract class AbsSeekBar extends ProgressBar { void onStopTrackingTouch() { } + /** + * Called when the user changes the seekbar's progress by using a key event. + */ + void onKeyChange() { + } + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { int progress = getProgress(); @@ -361,11 +366,13 @@ public abstract class AbsSeekBar extends ProgressBar { case KeyEvent.KEYCODE_DPAD_LEFT: if (progress <= 0) break; setProgress(progress - mKeyProgressIncrement, true); + onKeyChange(); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: if (progress >= getMax()) break; setProgress(progress + mKeyProgressIncrement, true); + onKeyChange(); return true; } diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index 8aafee2..2bb716c 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -56,6 +56,7 @@ public class FrameLayout extends ViewGroup { private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); private int mForegroundGravity = Gravity.FILL; + private boolean mForegroundInPadding = true; public FrameLayout(Context context) { super(context); @@ -71,6 +72,9 @@ public class FrameLayout extends ViewGroup { TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.FrameLayout, defStyle, 0); + mForegroundGravity = a.getInt( + com.android.internal.R.styleable.FrameLayout_foregroundGravity, mForegroundGravity); + final Drawable d = a.getDrawable(com.android.internal.R.styleable.FrameLayout_foreground); if (d != null) { setForeground(d); @@ -80,8 +84,8 @@ public class FrameLayout extends ViewGroup { setMeasureAllChildren(true); } - mForegroundGravity = a.getInt(com.android.internal.R.styleable.FrameLayout_foregroundGravity, - mForegroundGravity); + mForegroundInPadding = a.getBoolean( + com.android.internal.R.styleable.FrameLayout_foregroundInsidePadding, true); a.recycle(); } @@ -105,6 +109,23 @@ public class FrameLayout extends ViewGroup { } mForegroundGravity = foregroundGravity; + + + if (mForegroundGravity == Gravity.FILL && mForeground != null) { + Rect padding = new Rect(); + if (mForeground.getPadding(padding)) { + mForegroundPaddingLeft = padding.left; + mForegroundPaddingTop = padding.top; + mForegroundPaddingRight = padding.right; + mForegroundPaddingBottom = padding.bottom; + } + } else { + mForegroundPaddingLeft = 0; + mForegroundPaddingTop = 0; + mForegroundPaddingRight = 0; + mForegroundPaddingBottom = 0; + } + requestLayout(); } } @@ -167,12 +188,14 @@ public class FrameLayout extends ViewGroup { if (drawable.isStateful()) { drawable.setState(getDrawableState()); } - Rect padding = new Rect(); - if (drawable.getPadding(padding)) { - mForegroundPaddingLeft = padding.left; - mForegroundPaddingTop = padding.top; - mForegroundPaddingRight = padding.right; - mForegroundPaddingBottom = padding.bottom; + if (mForegroundGravity == Gravity.FILL) { + Rect padding = new Rect(); + if (drawable.getPadding(padding)) { + mForegroundPaddingLeft = padding.left; + mForegroundPaddingTop = padding.top; + mForegroundPaddingRight = padding.right; + mForegroundPaddingBottom = padding.bottom; + } } } else { setWillNotDraw(true); @@ -309,10 +332,14 @@ public class FrameLayout extends ViewGroup { final Rect selfBounds = mSelfBounds; final Rect overlayBounds = mOverlayBounds; - selfBounds.set(0, 0, w, h); + if (mForegroundInPadding) { + selfBounds.set(0, 0, w, h); + } else { + selfBounds.set(mPaddingLeft, mPaddingTop, w - mPaddingRight, h - mPaddingBottom); + } + Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), foreground.getIntrinsicHeight(), selfBounds, overlayBounds); - foreground.setBounds(overlayBounds); } } diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index 227fb95..a2ec83f 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -405,6 +405,8 @@ public class MediaController extends FrameLayout { return super.dispatchKeyEvent(event); } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { hide(); + + return true; } else { show(sDefaultTimeout); } diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index a4f729f..f864690 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1125,17 +1125,16 @@ public class PopupWindow { } /** - * <p>Updates the position and the dimension of the popup window. Width and - * height can be set to -1 to update location only. Calling this function - * also updates the window with the current popup state as - * described for {@link #update()}.</p> + * <p>Updates the position and the dimension of the popup window. Calling this + * function also updates the window with the current popup state as described + * for {@link #update()}.</p> * * @param anchor the popup's anchor view * @param width the new width, can be -1 to ignore * @param height the new height, can be -1 to ignore */ public void update(View anchor, int width, int height) { - update(anchor, 0, 0, width, height); + update(anchor, false, 0, 0, true, width, height); } /** @@ -1153,31 +1152,44 @@ public class PopupWindow { * @param height the new height, can be -1 to ignore */ public void update(View anchor, int xoff, int yoff, int width, int height) { + update(anchor, true, xoff, yoff, true, width, height); + } + + private void update(View anchor, boolean updateLocation, int xoff, int yoff, + boolean updateDimension, int width, int height) { + if (!isShowing() || mContentView == null) { return; } WeakReference<View> oldAnchor = mAnchor; if (oldAnchor == null || oldAnchor.get() != anchor || - mAnchorXoff != xoff || mAnchorYoff != yoff) { + (updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff))) { registerForScrollChanged(anchor, xoff, yoff); } WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams(); - if (width == -1) { - width = mPopupWidth; - } else { - mPopupWidth = width; + if (updateDimension) { + if (width == -1) { + width = mPopupWidth; + } else { + mPopupWidth = width; + } + if (height == -1) { + height = mPopupHeight; + } else { + mPopupHeight = height; + } } - if (height == -1) { - height = mPopupHeight; + + if (updateLocation) { + mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); } else { - mPopupHeight = height; + mAboveAnchor = findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff); } - - mAboveAnchor = findDropDownPosition(anchor, p, xoff, yoff); + update(p.x, p.y, width, height); } diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index ad5ca07..1800c5a 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -296,7 +296,13 @@ public class RatingBar extends AbsSeekBar { dispatchRatingChange(true); } } - + + @Override + void onKeyChange() { + super.onKeyChange(); + dispatchRatingChange(true); + } + void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 52c421c..c4f0abd 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -821,7 +821,10 @@ public class RelativeLayout extends ViewGroup { @ViewDebug.IntToString(from = CENTER_VERTICAL, to = "centerVertical"), @ViewDebug.IntToString(from = LEFT_OF, to = "leftOf"), @ViewDebug.IntToString(from = RIGHT_OF, to = "rightOf") - }, mapping = { @ViewDebug.IntToString(from = TRUE, to = "true") }) + }, mapping = { + @ViewDebug.IntToString(from = TRUE, to = "true"), + @ViewDebug.IntToString(from = 0, to = "NO_ID") + }) private int[] mRules = new int[VERB_COUNT]; private int mLeft, mTop, mRight, mBottom; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 7b62b50..136752b 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -224,7 +224,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CharSequence mError; private boolean mErrorWasChanged; - private PopupWindow mPopup; + private ErrorPopup mPopup; /** * This flag is set if the TextView tries to display an error before it * is attached to the window (so its position is still unknown). @@ -3039,12 +3039,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } } - } - if (ict != null || !shouldAdvanceFocusOnEnter()) { + // This is the handling for some default action. // Note that for backwards compatibility we don't do this // default handling if explicit ime options have not been given, - // to instead turn this into the normal enter key codes that an + // instead turning this into the normal enter key codes that an // app may be expecting. if (actionCode == EditorInfo.IME_ACTION_NEXT) { View v = focusSearch(FOCUS_DOWN); @@ -3066,15 +3065,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } Handler h = getHandler(); - long eventTime = SystemClock.uptimeMillis(); - h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, - new KeyEvent(eventTime, eventTime, - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, - KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE))); - h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, - new KeyEvent(SystemClock.uptimeMillis(), eventTime, - KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, - KeyEvent.FLAG_SOFT_KEYBOARD|KeyEvent.FLAG_KEEP_TOUCH_MODE))); + if (h != null) { + long eventTime = SystemClock.uptimeMillis(); + h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, + new KeyEvent(eventTime, eventTime, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, + KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION))); + h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, + new KeyEvent(SystemClock.uptimeMillis(), eventTime, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, + KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE + | KeyEvent.FLAG_EDITOR_ACTION))); + } } /** @@ -3222,25 +3225,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint, null); - mPopup = new PopupWindow(err, 200, 50) { - private boolean mAbove = false; - - @Override - public void update(int x, int y, int w, int h, boolean force) { - super.update(x, y, w, h, force); - - boolean above = isAboveAnchor(); - if (above != mAbove) { - mAbove = above; - - if (above) { - err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above); - } else { - err.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error); - } - } - } - }; + mPopup = new ErrorPopup(err, 200, 50); mPopup.setFocusable(false); // The user is entering text, so the input method is needed. We // don't want the popup to be displayed on top of it. @@ -3252,6 +3237,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener tv.setText(mError); mPopup.showAsDropDown(this, getErrorX(), getErrorY()); + mPopup.fixDirection(mPopup.isAboveAnchor()); + } + + private static class ErrorPopup extends PopupWindow { + private boolean mAbove = false; + private TextView mView; + + ErrorPopup(TextView v, int width, int height) { + super(v, width, height); + mView = v; + } + + void fixDirection(boolean above) { + mAbove = above; + + if (above) { + mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error_above); + } else { + mView.setBackgroundResource(com.android.internal.R.drawable.popup_inline_error); + } + } + + @Override + public void update(int x, int y, int w, int h, boolean force) { + super.update(x, y, w, h, force); + + boolean above = isAboveAnchor(); + if (above != mAbove) { + fixDirection(above); + } + } } /** @@ -4001,7 +4017,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { - KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); + KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); int which = doKeyDown(keyCode, down, event); if (which == 0) { @@ -4020,7 +4036,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // of down and up events until we have done the complete repeatCount. // It would be nice if those interfaces had an onKeyMultiple() method, // but adding that is a more complicated change. - KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); + KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); if (which == 1) { mInput.onKeyUp(this, (Editable)mText, keyCode, up); while (--repeatCount > 0) { @@ -4069,19 +4085,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - private boolean isInterestingEnter(KeyEvent event) { - if ((event.getFlags() & KeyEvent.FLAG_SOFT_KEYBOARD) != 0 && - mInputContentType != null && - (mInputContentType.imeOptions & - EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { - // If this enter key came from a soft keyboard, and the - // text editor has been configured to not do a default - // action for software enter keys, then we aren't interested. - return false; - } - return true; - } - private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { if (!isEnabled()) { return 0; @@ -4089,18 +4092,37 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (keyCode) { case KeyEvent.KEYCODE_ENTER: - if (!isInterestingEnter(event)) { - // Ignore enter key we aren't interested in. - return -1; - } - if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0 - && mInputContentType != null - && mInputContentType.onEditorActionListener != null) { - mInputContentType.enterDown = true; - // We are consuming the enter key for them. - return -1; + // If ALT modifier is held, then we always insert a + // newline character. + if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) { + + // When mInputContentType is set, we know that we are + // running in a "modern" cupcake environment, so don't need + // to worry about the application trying to capture + // enter key events. + if (mInputContentType != null) { + + // If there is an action listener, given them a + // chance to consume the event. + if (mInputContentType.onEditorActionListener != null && + mInputContentType.onEditorActionListener.onEditorAction( + this, EditorInfo.IME_NULL, event)) { + mInputContentType.enterDown = true; + // We are consuming the enter key for them. + return -1; + } + } + + // If our editor should move focus when enter is pressed, or + // this is a generated event from an IME action button, then + // don't let it be inserted into the text. + if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0 + || shouldAdvanceFocusOnEnter()) { + return -1; + } } - // fall through... + break; + case KeyEvent.KEYCODE_DPAD_CENTER: if (shouldAdvanceFocusOnEnter()) { return 0; @@ -4215,7 +4237,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - if (shouldAdvanceFocusOnEnter()) { + if ((event.getFlags()&KeyEvent.FLAG_EDITOR_ACTION) != 0 + || shouldAdvanceFocusOnEnter()) { /* * If there is a click listener, just call through to * super, which will invoke it. @@ -4243,7 +4266,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onKeyUp(keyCode, event); return true; } else if ((event.getFlags() - & KeyEvent.FLAG_SOFT_KEYBOARD) != 0) { + & KeyEvent.FLAG_EDITOR_ACTION) != 0) { // No target for next focus, but make sure the IME // if this came from it. InputMethodManager imm = InputMethodManager.peekInstance(); @@ -4302,6 +4325,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; } } + if ((outAttrs.inputType & (InputType.TYPE_MASK_CLASS + | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) + == (InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_FLAG_MULTI_LINE)) { + // Multi-line text editors should always show an enter key. + outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; + } outAttrs.hintText = mHint; if (mText instanceof Editable) { InputConnection ic = new EditableInputConnection(this); diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index 0df919d..c5fa18c 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -19,15 +19,11 @@ package android.widget; import android.content.Context; import android.os.Handler; import android.util.AttributeSet; -import android.view.GestureDetector; -import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.GestureDetector.SimpleOnGestureListener; import android.view.View.OnLongClickListener; - public class ZoomButton extends ImageButton implements OnLongClickListener { private final Handler mHandler; @@ -39,7 +35,6 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { } } }; - private final GestureDetector mGestureDetector; private long mZoomSpeed = 1000; private boolean mIsInLongpress; @@ -55,19 +50,11 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { public ZoomButton(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mHandler = new Handler(); - mGestureDetector = new GestureDetector(context, new SimpleOnGestureListener() { - @Override - public void onLongPress(MotionEvent e) { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - onLongClick(ZoomButton.this); - } - }); setOnLongClickListener(this); } @Override public boolean onTouchEvent(MotionEvent event) { - mGestureDetector.onTouchEvent(event); if ((event.getAction() == MotionEvent.ACTION_CANCEL) || (event.getAction() == MotionEvent.ACTION_UP)) { mIsInLongpress = false; diff --git a/core/java/android/widget/ZoomButtonsController.java b/core/java/android/widget/ZoomButtonsController.java index 4daa419..d9fb78b 100644 --- a/core/java/android/widget/ZoomButtonsController.java +++ b/core/java/android/widget/ZoomButtonsController.java @@ -16,23 +16,15 @@ package android.widget; -import android.app.AlertDialog; -import android.app.Dialog; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.graphics.Canvas; import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Handler; import android.os.Message; -import android.os.SystemClock; -import android.provider.Settings; import android.util.Log; -import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -42,7 +34,6 @@ import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewRoot; -import android.view.Window; import android.view.WindowManager; import android.view.View.OnClickListener; import android.view.WindowManager.LayoutParams; @@ -51,29 +42,33 @@ import android.view.WindowManager.LayoutParams; * Implementation notes: * - The zoom controls are displayed in their own window. * (Easier for the client and better performance) - * - This window is not touchable, and by default is not focusable. - * - To make the buttons clickable, it attaches a OnTouchListener to the owner - * view and does the hit detection locally. + * - This window is never touchable, and by default is not focusable. + * Its rect is quite big (fills horizontally) but has empty space between the + * edges and center. Touches there should be given to the owner. Instead of + * having the window touchable and dispatching these empty touch events to the + * owner, we set the window to not touchable and steal events from owner + * via onTouchListener. + * - To make the buttons clickable, it attaches an OnTouchListener to the owner + * view and does the hit detection locally (attaches when visible, detaches when invisible). * - When it is focusable, it forwards uninteresting events to the owner view's * view hierarchy. */ /** * The {@link ZoomButtonsController} handles showing and hiding the zoom - * controls relative to an owner view. It also gives the client access to the - * zoom controls container, allowing for additional accessory buttons to be - * shown in the zoom controls window. + * controls and positioning it relative to an owner view. It also gives the + * client access to the zoom controls container, allowing for additional + * accessory buttons to be shown in the zoom controls window. * <p> - * Typical usage involves the client using the {@link GestureDetector} to - * forward events from - * {@link GestureDetector.OnDoubleTapListener#onDoubleTapEvent(MotionEvent)} to - * {@link #handleDoubleTapEvent(MotionEvent)}. Also, whenever the owner cannot - * be zoomed further, the client should update + * Typically, clients should call {@link #setVisible(boolean) setVisible(true)} + * on a touch down or move (no need to call {@link #setVisible(boolean) + * setVisible(false)} since it will time out on its own). Also, whenever the + * owner cannot be zoomed further, the client should update * {@link #setZoomInEnabled(boolean)} and {@link #setZoomOutEnabled(boolean)}. * <p> * If you are using this with a custom View, please call * {@link #setVisible(boolean) setVisible(false)} from the * {@link View#onDetachedFromWindow}. - * + * * @hide */ public class ZoomButtonsController implements View.OnTouchListener { @@ -88,6 +83,7 @@ public class ZoomButtonsController implements View.OnTouchListener { private Context mContext; private WindowManager mWindowManager; + private boolean mAutoDismissControls = true; /** * The view that is being zoomed by this zoom controller. @@ -118,35 +114,26 @@ public class ZoomButtonsController implements View.OnTouchListener { * The {@link #mTouchTargetView}'s location in window, set on touch down. */ private int[] mTouchTargetWindowLocation = new int[2]; + /** * If the zoom controller is dismissed but the user is still in a touch * interaction, we set this to true. This will ignore all touch events until * up/cancel, and then set the owner's touch listener to null. + * <p> + * Otherwise, the owner view would get mismatched events (i.e., touch move + * even though it never got the touch down.) */ private boolean mReleaseTouchListenerOnUp; - /** - * Whether we are currently in the double-tap gesture, with the second tap - * still being performed (i.e., we're waiting for the second tap's touch up). - */ - private boolean mIsSecondTapDown; - /** Whether the container has been added to the window manager. */ private boolean mIsVisible; private Rect mTempRect = new Rect(); private int[] mTempIntArray = new int[2]; - + private OnZoomListener mCallback; /** - * In 1.0, the ZoomControls were to be added to the UI by the client of - * WebView, MapView, etc. We didn't want apps to break, so we return a dummy - * view in place now. - */ - private InvisibleView mDummyZoomControls; - - /** * When showing the zoom, we add the view as a new window. However, there is * logic that needs to know the size of the zoom which is determined after * it's laid out. Therefore, we must post this logic onto the UI thread so @@ -170,12 +157,6 @@ public class ZoomButtonsController implements View.OnTouchListener { } }; - /** - * The setting name that tracks whether we've shown the zoom tutorial. - */ - private static final String SETTING_NAME_SHOWN_TUTORIAL = "shown_zoom_tutorial"; - private static Dialog sTutorialDialog; - /** When configuration changes, this is called after the UI thread is idle. */ private static final int MSG_POST_CONFIGURATION_CHANGED = 2; /** Used to delay the zoom controller dismissal. */ @@ -215,7 +196,7 @@ public class ZoomButtonsController implements View.OnTouchListener { /** * Constructor for the {@link ZoomButtonsController}. - * + * * @param ownerView The view that is being zoomed by the zoom controls. The * zoom controls will be displayed aligned with this view. */ @@ -227,13 +208,13 @@ public class ZoomButtonsController implements View.OnTouchListener { mTouchPaddingScaledSq = (int) (ZOOM_CONTROLS_TOUCH_PADDING * mContext.getResources().getDisplayMetrics().density); mTouchPaddingScaledSq *= mTouchPaddingScaledSq; - + mContainer = createContainer(); } /** * Whether to enable the zoom in control. - * + * * @param enabled Whether to enable the zoom in control. */ public void setZoomInEnabled(boolean enabled) { @@ -242,7 +223,7 @@ public class ZoomButtonsController implements View.OnTouchListener { /** * Whether to enable the zoom out control. - * + * * @param enabled Whether to enable the zoom out control. */ public void setZoomOutEnabled(boolean enabled) { @@ -251,7 +232,7 @@ public class ZoomButtonsController implements View.OnTouchListener { /** * Sets the delay between zoom callbacks as the user holds a zoom button. - * + * * @param speed The delay in milliseconds between zoom callbacks. */ public void setZoomSpeed(long speed) { @@ -264,7 +245,8 @@ public class ZoomButtonsController implements View.OnTouchListener { lp.gravity = Gravity.TOP | Gravity.LEFT; lp.flags = LayoutParams.FLAG_NOT_TOUCHABLE | LayoutParams.FLAG_NOT_FOCUSABLE | - LayoutParams.FLAG_LAYOUT_NO_LIMITS; + LayoutParams.FLAG_LAYOUT_NO_LIMITS | + LayoutParams.FLAG_ALT_FOCUSABLE_IM; lp.height = LayoutParams.WRAP_CONTENT; lp.width = LayoutParams.FILL_PARENT; lp.type = LayoutParams.TYPE_APPLICATION_PANEL; @@ -299,7 +281,7 @@ public class ZoomButtonsController implements View.OnTouchListener { /** * Sets the {@link OnZoomListener} listener that receives callbacks to zoom. - * + * * @param listener The listener that will be told to zoom. */ public void setOnZoomListener(OnZoomListener listener) { @@ -310,7 +292,7 @@ public class ZoomButtonsController implements View.OnTouchListener { * Sets whether the zoom controls should be focusable. If the controls are * focusable, then trackball and arrow key interactions are possible. * Otherwise, only touch interactions are possible. - * + * * @param focusable Whether the zoom controls should be focusable. */ public void setFocusable(boolean focusable) { @@ -327,8 +309,26 @@ public class ZoomButtonsController implements View.OnTouchListener { } /** + * Whether the zoom controls will be automatically dismissed after showing. + * + * @return Whether the zoom controls will be auto dismissed after showing. + */ + public boolean isAutoDismissed() { + return mAutoDismissControls; + } + + /** + * Sets whether the zoom controls will be automatically dismissed after + * showing. + */ + public void setAutoDismissed(boolean autoDismiss) { + if (mAutoDismissControls == autoDismiss) return; + mAutoDismissControls = autoDismiss; + } + + /** * Whether the zoom controls are visible to the user. - * + * * @return Whether the zoom controls are visible to the user. */ public boolean isVisible() { @@ -337,7 +337,7 @@ public class ZoomButtonsController implements View.OnTouchListener { /** * Sets whether the zoom controls should be visible to the user. - * + * * @param visible Whether the zoom controls should be visible to the user. */ public void setVisible(boolean visible) { @@ -419,7 +419,7 @@ public class ZoomButtonsController implements View.OnTouchListener { * <p> * The client can add other views to this container to link them with the * zoom controls. - * + * * @return The container of the zoom controls. It will be a layout that * respects the gravity of a child's layout parameters. */ @@ -427,43 +427,20 @@ public class ZoomButtonsController implements View.OnTouchListener { return mContainer; } - private void dismissControlsDelayed(int delay) { - mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS); - mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay); - } - /** - * Should be called by the client for each event belonging to the second tap - * (the down, move, up, and/or cancel events). + * Gets the view for the zoom controls. * - * @param event The event belonging to the second tap. - * @return Whether the event was consumed. + * @return The zoom controls view. */ - public boolean handleDoubleTapEvent(MotionEvent event) { - int action = event.getAction(); + public View getZoomControls() { + return mControls; + } - if (action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - /* - * This class will consume all events in the second tap (down, - * move(s), up). But, the owner already got the second tap's down, - * so cancel that. Do this before setVisible, since that call - * will set us as a touch listener. - */ - MotionEvent cancelEvent = MotionEvent.obtain(event.getDownTime(), - SystemClock.elapsedRealtime(), - MotionEvent.ACTION_CANCEL, 0, 0, 0); - mOwnerView.dispatchTouchEvent(cancelEvent); - cancelEvent.recycle(); - - setVisible(true); - centerPoint(x, y); - mIsSecondTapDown = true; + private void dismissControlsDelayed(int delay) { + if (mAutoDismissControls) { + mHandler.removeMessages(MSG_DISMISS_ZOOM_CONTROLS); + mHandler.sendEmptyMessageDelayed(MSG_DISMISS_ZOOM_CONTROLS, delay); } - - return true; } private void refreshPositioningVariables() { @@ -477,7 +454,7 @@ public class ZoomButtonsController implements View.OnTouchListener { mOwnerView.getLocationOnScreen(mOwnerViewRawLocation); mContainerRawLocation[0] = mOwnerViewRawLocation[0]; mContainerRawLocation[1] = mOwnerViewRawLocation[1] + containerOwnerYOffset; - + int[] ownerViewWindowLoc = mTempIntArray; mOwnerView.getLocationInWindow(ownerViewWindowLoc); @@ -488,39 +465,30 @@ public class ZoomButtonsController implements View.OnTouchListener { if (mIsVisible) { mWindowManager.updateViewLayout(mContainer, mContainerLayoutParams); } - - } - /** - * Centers the point (in owner view's coordinates). - */ - private void centerPoint(int x, int y) { - if (mCallback != null) { - mCallback.onCenter(x, y); - } } /* This will only be called when the container has focus. */ private boolean onContainerKey(KeyEvent event) { int keyCode = event.getKeyCode(); if (isInterestingKey(keyCode)) { - + if (keyCode == KeyEvent.KEYCODE_BACK) { setVisible(false); } else { dismissControlsDelayed(ZOOM_CONTROLS_TIMEOUT); } - + // Let the container handle the key return false; - + } else { - + ViewRoot viewRoot = getOwnerViewRoot(); if (viewRoot != null) { viewRoot.dispatchKey(event); } - + // We gave the key to the owner, don't let the container handle this key return true; } @@ -540,13 +508,13 @@ public class ZoomButtonsController implements View.OnTouchListener { return false; } } - + private ViewRoot getOwnerViewRoot() { View rootViewOfOwner = mOwnerView.getRootView(); if (rootViewOfOwner == null) { return null; } - + ViewParent parentOfRootView = rootViewOfOwner.getParent(); if (parentOfRootView instanceof ViewRoot) { return (ViewRoot) parentOfRootView; @@ -562,13 +530,6 @@ public class ZoomButtonsController implements View.OnTouchListener { public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); - // Consume all events during the second-tap interaction (down, move, up/cancel) - boolean consumeEvent = mIsSecondTapDown; - if ((action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL)) { - // The second tap can no longer be down - mIsSecondTapDown = false; - } - if (mReleaseTouchListenerOnUp) { // The controls were dismissed but we need to throw away all events until the up if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { @@ -609,18 +570,21 @@ public class ZoomButtonsController implements View.OnTouchListener { mOwnerViewRawLocation[1] - targetViewRawY); /* Disallow negative coordinates (which can occur due to * ZOOM_CONTROLS_TOUCH_PADDING) */ - if (containerEvent.getX() < 0) { - containerEvent.offsetLocation(-containerEvent.getX(), 0); + // These are floats because we need to potentially offset away this exact amount + float containerX = containerEvent.getX(); + float containerY = containerEvent.getY(); + if (containerX < 0 && containerX > -ZOOM_CONTROLS_TOUCH_PADDING) { + containerEvent.offsetLocation(-containerX, 0); } - if (containerEvent.getY() < 0) { - containerEvent.offsetLocation(0, -containerEvent.getY()); + if (containerY < 0 && containerY > -ZOOM_CONTROLS_TOUCH_PADDING) { + containerEvent.offsetLocation(0, -containerY); } boolean retValue = targetView.dispatchTouchEvent(containerEvent); containerEvent.recycle(); - return retValue || consumeEvent; + return retValue; } else { - return consumeEvent; + return false; } } @@ -646,7 +610,7 @@ public class ZoomButtonsController implements View.OnTouchListener { View closestChild = null; int closestChildDistanceSq = Integer.MAX_VALUE; - + for (int i = mContainer.getChildCount() - 1; i >= 0; i--) { View child = mContainer.getChildAt(i); if (child.getVisibility() != View.VISIBLE) { @@ -657,13 +621,23 @@ public class ZoomButtonsController implements View.OnTouchListener { if (frame.contains(containerCoordsX, containerCoordsY)) { return child; } - - int distanceX = Math.min(Math.abs(frame.left - containerCoordsX), + + int distanceX; + if (containerCoordsX >= frame.left && containerCoordsX <= frame.right) { + distanceX = 0; + } else { + distanceX = Math.min(Math.abs(frame.left - containerCoordsX), Math.abs(containerCoordsX - frame.right)); - int distanceY = Math.min(Math.abs(frame.top - containerCoordsY), - Math.abs(containerCoordsY - frame.bottom)); + } + int distanceY; + if (containerCoordsY >= frame.top && containerCoordsY <= frame.bottom) { + distanceY = 0; + } else { + distanceY = Math.min(Math.abs(frame.top - containerCoordsY), + Math.abs(containerCoordsY - frame.bottom)); + } int distanceSq = distanceX * distanceX + distanceY * distanceY; - + if ((distanceSq < mTouchPaddingScaledSq) && (distanceSq < closestChildDistanceSq)) { closestChild = child; @@ -679,130 +653,27 @@ public class ZoomButtonsController implements View.OnTouchListener { refreshPositioningVariables(); } - /* - * This is static so Activities can call this instead of the Views - * (Activities usually do not have a reference to the ZoomButtonsController - * instance.) - */ - /** - * Shows a "tutorial" (some text) to the user teaching her the new zoom - * invocation method. Must call from the main thread. - * <p> - * It checks the global system setting to ensure this has not been seen - * before. Furthermore, if the application does not have privilege to write - * to the system settings, it will store this bit locally in a shared - * preference. - * - * @hide This should only be used by our main apps--browser, maps, and - * gallery - */ - public static void showZoomTutorialOnce(Context context) { - - // TODO: remove this code, but to hit the weekend build, just never show - if (true) return; - - ContentResolver cr = context.getContentResolver(); - if (Settings.System.getInt(cr, SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { - return; - } - - SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); - if (sp.getInt(SETTING_NAME_SHOWN_TUTORIAL, 0) == 1) { - return; - } - - if (sTutorialDialog != null && sTutorialDialog.isShowing()) { - sTutorialDialog.dismiss(); - } - - LayoutInflater layoutInflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - TextView textView = (TextView) layoutInflater.inflate( - com.android.internal.R.layout.alert_dialog_simple_text, null) - .findViewById(android.R.id.text1); - textView.setText(com.android.internal.R.string.tutorial_double_tap_to_zoom_message_short); - - sTutorialDialog = new AlertDialog.Builder(context) - .setView(textView) - .setIcon(0) - .create(); - - Window window = sTutorialDialog.getWindow(); - window.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | - WindowManager.LayoutParams.FLAG_BLUR_BEHIND); - window.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); - - sTutorialDialog.show(); - } - - /** @hide Should only be used by Android platform apps */ - public static void finishZoomTutorial(Context context, boolean userNotified) { - if (sTutorialDialog == null) return; - - sTutorialDialog.dismiss(); - sTutorialDialog = null; - - // Record that they have seen the tutorial - if (userNotified) { - try { - Settings.System.putInt(context.getContentResolver(), SETTING_NAME_SHOWN_TUTORIAL, - 1); - } catch (SecurityException e) { - /* - * The app does not have permission to clear this global flag, make - * sure the user does not see the message when he comes back to this - * same app at least. - */ - SharedPreferences sp = context.getSharedPreferences("_zoom", Context.MODE_PRIVATE); - sp.edit().putInt(SETTING_NAME_SHOWN_TUTORIAL, 1).commit(); - } - } - } - - /** @hide Should only be used by Android platform apps */ - public void finishZoomTutorial() { - finishZoomTutorial(mContext, true); - } - - /** @hide Should only be used only be WebView and MapView */ - public View getDummyZoomControls() { - if (mDummyZoomControls == null) { - mDummyZoomControls = new InvisibleView(mContext); - } - return mDummyZoomControls; - } - /** * Interface that will be called when the user performs an interaction that * triggers some action, for example zooming. */ public interface OnZoomListener { - /** - * Called when the given point should be centered. The point will be in - * owner view coordinates. - * - * @param x The x of the point. - * @param y The y of the point. - */ - void onCenter(int x, int y); - + /** * Called when the zoom controls' visibility changes. - * + * * @param visible Whether the zoom controls are visible. */ void onVisibilityChanged(boolean visible); - + /** * Called when the owner view needs to be zoomed. - * + * * @param zoomIn The direction of the zoom: true to zoom in, false to zoom out. */ void onZoom(boolean zoomIn); } - + private class Container extends FrameLayout { public Container(Context context) { super(context); @@ -820,29 +691,4 @@ public class ZoomButtonsController implements View.OnTouchListener { } } - /** - * An InvisibleView is an invisible, zero-sized View for backwards - * compatibility - */ - private final class InvisibleView extends View { - - private InvisibleView(Context context) { - super(context); - setVisibility(GONE); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(0, 0); - } - - @Override - public void draw(Canvas canvas) { - } - - @Override - protected void dispatchDraw(Canvas canvas) { - } - } - } diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java index 84d8f0e..a12aee5 100644 --- a/core/java/android/widget/ZoomControls.java +++ b/core/java/android/widget/ZoomControls.java @@ -30,11 +30,6 @@ import com.android.internal.R; /** * The {@code ZoomControls} class displays a simple set of controls used for zooming and * provides callbacks to register for events. */ -// TODO: pending API council -// * <p> -// * Instead of using this directly, consider using the {@link ZoomButtonsController} which -// * handles displaying the zoom controls. -// */ @Widget public class ZoomControls extends LinearLayout { |