summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-18 17:39:46 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-18 17:39:46 -0700
commit105925376f8d0f6b318c9938c7b83ef7fef094da (patch)
tree3b19ee2bd8704cb9c6a0da7e42dec6759183de6d /core/java/android
parentba87e3e6c985e7175152993b5efcc7dd2f0e1c93 (diff)
downloadframeworks_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')
-rw-r--r--core/java/android/app/ActivityThread.java17
-rw-r--r--core/java/android/app/ApplicationContext.java29
-rw-r--r--core/java/android/app/LauncherActivity.java4
-rw-r--r--core/java/android/app/Service.java4
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java17
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java50
-rw-r--r--core/java/android/bluetooth/BluetoothIntent.java14
-rw-r--r--core/java/android/bluetooth/IBluetoothDevice.aidl5
-rw-r--r--core/java/android/bluetooth/IBluetoothDeviceCallback.aidl2
-rw-r--r--core/java/android/bluetooth/ScoSocket.java20
-rw-r--r--core/java/android/content/BroadcastReceiver.java8
-rw-r--r--core/java/android/content/Intent.java85
-rw-r--r--core/java/android/content/pm/PackageManager.java8
-rw-r--r--core/java/android/emoji/EmojiFactory.java16
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java242
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java12
-rw-r--r--core/java/android/os/BatteryStats.java109
-rw-r--r--core/java/android/os/Hardware.java37
-rwxr-xr-xcore/java/android/os/IHardwareService.aidl12
-rw-r--r--core/java/android/os/Power.java28
-rw-r--r--core/java/android/os/RemoteCallbackList.java2
-rw-r--r--core/java/android/provider/Calendar.java5
-rw-r--r--core/java/android/provider/CallLog.java11
-rw-r--r--core/java/android/provider/Contacts.java19
-rw-r--r--core/java/android/server/BluetoothA2dpService.java33
-rw-r--r--core/java/android/server/BluetoothDeviceService.java288
-rw-r--r--core/java/android/server/BluetoothEventLoop.java18
-rw-r--r--core/java/android/text/Html.java69
-rw-r--r--core/java/android/text/Layout.java105
-rw-r--r--core/java/android/text/StaticLayout.java39
-rw-r--r--core/java/android/view/GestureDetector.java2
-rw-r--r--core/java/android/view/IWindowManager.aidl3
-rw-r--r--core/java/android/view/KeyEvent.java72
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/ViewDebug.java68
-rw-r--r--core/java/android/view/ViewRoot.java24
-rw-r--r--core/java/android/view/ViewTreeObserver.java106
-rw-r--r--core/java/android/view/inputmethod/EditorInfo.java29
-rw-r--r--core/java/android/webkit/CacheManager.java28
-rw-r--r--core/java/android/webkit/HttpAuthHandler.java21
-rw-r--r--core/java/android/webkit/LoadListener.java41
-rw-r--r--core/java/android/webkit/MimeTypeMap.java1
-rw-r--r--core/java/android/webkit/Network.java18
-rw-r--r--core/java/android/webkit/SslErrorHandler.java20
-rw-r--r--core/java/android/webkit/WebSettings.java15
-rw-r--r--core/java/android/webkit/WebView.java211
-rw-r--r--core/java/android/widget/AbsListView.java8
-rw-r--r--core/java/android/widget/AbsSeekBar.java17
-rw-r--r--core/java/android/widget/FrameLayout.java47
-rw-r--r--core/java/android/widget/MediaController.java2
-rw-r--r--core/java/android/widget/PopupWindow.java42
-rw-r--r--core/java/android/widget/RatingBar.java8
-rw-r--r--core/java/android/widget/RelativeLayout.java5
-rw-r--r--core/java/android/widget/TextView.java150
-rw-r--r--core/java/android/widget/ZoomButton.java13
-rw-r--r--core/java/android/widget/ZoomButtonsController.java352
-rw-r--r--core/java/android/widget/ZoomControls.java5
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 {