summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/16.txt2
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/app/Notification.java7
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java7
-rw-r--r--core/java/android/hardware/input/KeyboardLayout.java26
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java7
-rw-r--r--core/java/android/nfc/NfcActivityManager.java18
-rw-r--r--core/java/android/nfc/NfcAdapter.java137
-rw-r--r--core/java/android/os/storage/IMountService.java9
-rw-r--r--core/java/android/preference/DialogPreference.java2
-rw-r--r--core/java/android/speech/RecognizerIntent.java39
-rw-r--r--core/java/android/view/View.java24
-rw-r--r--core/java/android/view/WindowManager.java7
-rw-r--r--core/java/android/webkit/AccessibilityInjector.java819
-rw-r--r--core/java/android/webkit/AccessibilityInjectorFallback.java575
-rw-r--r--core/java/android/webkit/WebCoreThreadWatchdog.java153
-rw-r--r--core/java/android/webkit/WebView.java9
-rw-r--r--core/java/android/webkit/WebViewClassic.java446
-rw-r--r--core/java/android/webkit/WebViewCore.java5
-rw-r--r--core/java/android/webkit/WebViewInputDispatcher.java34
-rw-r--r--core/java/android/webkit/WebViewProvider.java2
-rw-r--r--core/jni/android/graphics/BitmapFactory.h1
-rw-r--r--core/jni/android/graphics/BitmapRegionDecoder.cpp29
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.pngbin627 -> 549 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_activated_holo_light.9.pngbin634 -> 549 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.pngbin497 -> 535 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_disabled_holo_light.9.pngbin483 -> 535 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_holo_dark.9.pngbin610 -> 559 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_holo_light.9.pngbin573 -> 559 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.pngbin641 -> 566 bytes
-rw-r--r--core/res/res/drawable-hdpi/switch_thumb_pressed_holo_light.9.pngbin645 -> 566 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.pngbin503 -> 351 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_activated_holo_light.9.pngbin507 -> 351 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.pngbin404 -> 350 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_disabled_holo_light.9.pngbin390 -> 350 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_holo_dark.9.pngbin494 -> 362 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_holo_light.9.pngbin450 -> 362 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.pngbin518 -> 362 bytes
-rw-r--r--core/res/res/drawable-mdpi/switch_thumb_pressed_holo_light.9.pngbin520 -> 362 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.pngbin845 -> 653 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_activated_holo_light.9.pngbin851 -> 653 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.pngbin696 -> 633 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_light.9.pngbin694 -> 633 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_holo_dark.9.pngbin845 -> 687 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_holo_light.9.pngbin767 -> 687 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.pngbin870 -> 660 bytes
-rw-r--r--core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_light.9.pngbin873 -> 660 bytes
-rw-r--r--core/res/res/drawable/switch_track_holo_dark.xml1
-rw-r--r--core/res/res/drawable/switch_track_holo_light.xml1
-rw-r--r--core/tests/coretests/src/android/net/SSLTest.java13
-rw-r--r--graphics/java/android/graphics/BitmapRegionDecoder.java6
-rw-r--r--media/java/android/media/AudioManager.java31
-rw-r--r--media/java/android/media/AudioService.java174
-rw-r--r--media/java/android/media/IAudioService.aidl3
-rw-r--r--media/java/android/media/MediaPlayer.java2
-rw-r--r--media/java/android/media/MediaScanner.java2
-rw-r--r--packages/InputDevices/AndroidManifest.xml3
-rw-r--r--packages/InputDevices/res/values/strings.xml3
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.pngbin693 -> 827 bytes
-rw-r--r--packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_null.pngbin0 -> 1074 bytes
-rw-r--r--packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.pngbin537 -> 658 bytes
-rw-r--r--packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_null.pngbin0 -> 750 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.pngbin0 -> 953 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_wifi_signal_null.pngbin0 -> 1411 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.pngbin0 -> 735 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_wifi_signal_null.pngbin0 -> 1001 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.pngbin0 -> 1203 bytes
-rw-r--r--packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_wifi_signal_null.pngbin0 -> 1908 bytes
-rw-r--r--packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.pngbin864 -> 1054 bytes
-rw-r--r--packages/SystemUI/res/drawable-xhdpi/stat_sys_wifi_signal_null.pngbin0 -> 1407 bytes
-rw-r--r--packages/SystemUI/res/layout-sw600dp/super_status_bar.xml11
-rw-r--r--packages/SystemUI/res/layout/super_status_bar.xml9
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/styles.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java5
-rw-r--r--policy/src/com/android/internal/policy/impl/FaceUnlock.java32
-rw-r--r--policy/src/com/android/internal/policy/impl/KeyguardViewManager.java1
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java173
-rw-r--r--services/java/com/android/server/BackupManagerService.java65
-rw-r--r--services/java/com/android/server/MountService.java57
-rw-r--r--services/java/com/android/server/SystemServer.java33
-rw-r--r--services/java/com/android/server/WifiService.java63
-rw-r--r--services/java/com/android/server/input/InputManagerService.java18
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java27
-rwxr-xr-xservices/java/com/android/server/wm/WindowManagerService.java46
-rw-r--r--services/java/com/android/server/wm/WindowState.java3
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java19
-rw-r--r--tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java29
92 files changed, 2212 insertions, 1041 deletions
diff --git a/api/16.txt b/api/16.txt
index 1a694a8..c467c3b 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -19690,6 +19690,7 @@ package android.speech {
method public static final android.content.Intent getVoiceDetailsIntent(android.content.Context);
field public static final java.lang.String ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS";
field public static final java.lang.String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
+ field public static final java.lang.String ACTION_VOICE_SEARCH_HANDS_FREE = "android.speech.action.VOICE_SEARCH_HANDS_FREE";
field public static final java.lang.String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
field public static final java.lang.String DETAILS_META_DATA = "android.speech.DETAILS";
field public static final java.lang.String EXTRA_CALLING_PACKAGE = "calling_package";
@@ -19705,6 +19706,7 @@ package android.speech {
field public static final java.lang.String EXTRA_RESULTS = "android.speech.extra.RESULTS";
field public static final java.lang.String EXTRA_RESULTS_PENDINGINTENT = "android.speech.extra.RESULTS_PENDINGINTENT";
field public static final java.lang.String EXTRA_RESULTS_PENDINGINTENT_BUNDLE = "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
+ field public static final java.lang.String EXTRA_SECURE = "android.speech.extras.EXTRA_SECURE";
field public static final java.lang.String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
field public static final java.lang.String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
field public static final java.lang.String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
diff --git a/api/current.txt b/api/current.txt
index 1a694a8..c467c3b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19690,6 +19690,7 @@ package android.speech {
method public static final android.content.Intent getVoiceDetailsIntent(android.content.Context);
field public static final java.lang.String ACTION_GET_LANGUAGE_DETAILS = "android.speech.action.GET_LANGUAGE_DETAILS";
field public static final java.lang.String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH";
+ field public static final java.lang.String ACTION_VOICE_SEARCH_HANDS_FREE = "android.speech.action.VOICE_SEARCH_HANDS_FREE";
field public static final java.lang.String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
field public static final java.lang.String DETAILS_META_DATA = "android.speech.DETAILS";
field public static final java.lang.String EXTRA_CALLING_PACKAGE = "calling_package";
@@ -19705,6 +19706,7 @@ package android.speech {
field public static final java.lang.String EXTRA_RESULTS = "android.speech.extra.RESULTS";
field public static final java.lang.String EXTRA_RESULTS_PENDINGINTENT = "android.speech.extra.RESULTS_PENDINGINTENT";
field public static final java.lang.String EXTRA_RESULTS_PENDINGINTENT_BUNDLE = "android.speech.extra.RESULTS_PENDINGINTENT_BUNDLE";
+ field public static final java.lang.String EXTRA_SECURE = "android.speech.extras.EXTRA_SECURE";
field public static final java.lang.String EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS";
field public static final java.lang.String EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_MINIMUM_LENGTH_MILLIS";
field public static final java.lang.String EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS = "android.speech.extras.SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS";
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9a8d802..2eea171 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1852,14 +1852,17 @@ public class Notification implements Parcelable
int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
R.id.inbox_text4};
+ // Make sure all rows are gone in case we reuse a view.
+ for (int rowId : rowIds) {
+ contentView.setViewVisibility(rowId, View.GONE);
+ }
+
int i=0;
while (i < mTexts.size() && i < rowIds.length) {
CharSequence str = mTexts.get(i);
if (str != null && !str.equals("")) {
contentView.setViewVisibility(rowIds[i], View.VISIBLE);
contentView.setTextViewText(rowIds[i], str);
- } else {
- contentView.setViewVisibility(rowIds[i], View.GONE);
}
i++;
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6448b55..dfd35e1 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -77,7 +77,8 @@ public final class InputManager {
* The meta-data specifies a resource that contains a description of each keyboard
* layout that is provided by the application.
* <pre><code>
- * &lt;receiver android:name=".InputDeviceReceiver">
+ * &lt;receiver android:name=".InputDeviceReceiver"
+ * android:label="@string/keyboard_layouts_label">
* &lt;intent-filter>
* &lt;action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
* &lt;/intent-filter>
@@ -90,7 +91,9 @@ public final class InputManager {
* an XML resource whose root element is <code>&lt;keyboard-layouts></code> that
* contains zero or more <code>&lt;keyboard-layout></code> elements.
* Each <code>&lt;keyboard-layout></code> element specifies the name, label, and location
- * of a key character map for a particular keyboard layout.
+ * of a key character map for a particular keyboard layout. The label on the receiver
+ * is used to name the collection of keyboard layouts provided by this receiver in the
+ * keyboard layout settings.
* <pre></code>
* &lt;?xml version="1.0" encoding="utf-8"?>
* &lt;keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index e75a6dc..5402e75 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -28,6 +28,7 @@ public final class KeyboardLayout implements Parcelable,
Comparable<KeyboardLayout> {
private final String mDescriptor;
private final String mLabel;
+ private final String mCollection;
public static final Parcelable.Creator<KeyboardLayout> CREATOR =
new Parcelable.Creator<KeyboardLayout>() {
@@ -39,14 +40,16 @@ public final class KeyboardLayout implements Parcelable,
}
};
- public KeyboardLayout(String descriptor, String label) {
+ public KeyboardLayout(String descriptor, String label, String collection) {
mDescriptor = descriptor;
mLabel = label;
+ mCollection = collection;
}
private KeyboardLayout(Parcel source) {
mDescriptor = source.readString();
mLabel = source.readString();
+ mCollection = source.readString();
}
/**
@@ -68,6 +71,15 @@ public final class KeyboardLayout implements Parcelable,
return mLabel;
}
+ /**
+ * Gets the name of the collection to which the keyboard layout belongs. This is
+ * the label of the broadcast receiver or application that provided the keyboard layout.
+ * @return The keyboard layout collection name.
+ */
+ public String getCollection() {
+ return mCollection;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -77,15 +89,23 @@ public final class KeyboardLayout implements Parcelable,
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mDescriptor);
dest.writeString(mLabel);
+ dest.writeString(mCollection);
}
@Override
public int compareTo(KeyboardLayout another) {
- return mLabel.compareToIgnoreCase(another.mLabel);
+ int result = mLabel.compareToIgnoreCase(another.mLabel);
+ if (result == 0) {
+ result = mCollection.compareToIgnoreCase(another.mCollection);
+ }
+ return result;
}
@Override
public String toString() {
- return mLabel;
+ if (mCollection.isEmpty()) {
+ return mLabel;
+ }
+ return mLabel + " - " + mCollection;
}
} \ No newline at end of file
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 6a4f1f2..2703f1d 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -261,8 +261,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* server then the first protocol in the client's list will be selected.
* The order of the client's protocols is otherwise insignificant.
*
- * @param npnProtocols a possibly-empty list of protocol byte arrays. All
- * arrays must be non-empty and of length less than 256.
+ * @param npnProtocols a non-empty list of protocol byte arrays. All arrays
+ * must be non-empty and of length less than 256.
*/
public void setNpnProtocols(byte[][] npnProtocols) {
this.mNpnProtocols = toNpnProtocolsList(npnProtocols);
@@ -273,6 +273,9 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* strings.
*/
static byte[] toNpnProtocolsList(byte[]... npnProtocols) {
+ if (npnProtocols.length == 0) {
+ throw new IllegalArgumentException("npnProtocols.length == 0");
+ }
int totalLength = 0;
for (byte[] s : npnProtocols) {
if (s.length == 0 || s.length > 255) {
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index 7ffa575..53b41d5 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -299,7 +299,23 @@ public final class NfcActivityManager extends INdefPushCallback.Stub
callback = state.uriCallback;
}
if (callback != null) {
- return callback.createBeamUris(mDefaultEvent);
+ uris = callback.createBeamUris(mDefaultEvent);
+ if (uris != null) {
+ for (Uri uri : uris) {
+ if (uri == null) {
+ Log.e(TAG, "Uri not allowed to be null.");
+ return null;
+ }
+ String scheme = uri.getScheme();
+ if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
+ !scheme.equalsIgnoreCase("content"))) {
+ Log.e(TAG, "Uri needs to have " +
+ "either scheme file or scheme content");
+ return null;
+ }
+ }
+ }
+ return uris;
} else {
return uris;
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 7bf9feb..4464d58 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -584,17 +584,138 @@ public final class NfcAdapter {
}
}
- //TODO: make sure NFC service has permission for URI
- //TODO: see if we will eventually support multiple URIs
- //TODO: javadoc
+ /**
+ * Set one or more {@link Uri}s to send using Android Beam (TM). Every
+ * Uri you provide must have either scheme 'file' or scheme 'content'.
+ *
+ * <p>For the data provided through this method, Android Beam tries to
+ * switch to alternate transports such as Bluetooth to achieve a fast
+ * transfer speed. Hence this method is very suitable
+ * for transferring large files such as pictures or songs.
+ *
+ * <p>The receiving side will store the content of each Uri in
+ * a file and present a notification to the user to open the file
+ * with a {@link android.content.Intent} with action
+ * {@link android.content.Intent#ACTION_VIEW}.
+ * If multiple URIs are sent, the {@link android.content.Intent} will refer
+ * to the first of the stored files.
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the URI(s) are only made available for Android Beam when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+ * have priority over both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback}.
+ *
+ * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+ * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+ * then the Uri push will be completely disabled for the specified activity(s).
+ *
+ * <p>Code example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setBeamPushUris(new Uri[] {uri1, uri2}, this);
+ * }
+ * </pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the Uri(s) and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p>If your Activity wants to dynamically supply Uri(s),
+ * then set a callback using {@link #setBeamPushUrisCallback} instead
+ * of using this method.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param uris an array of Uri(s) to push over Android Beam
+ * @param activity activity for which the Uri(s) will be pushed
+ */
public void setBeamPushUris(Uri[] uris, Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
+ if (uris != null) {
+ for (Uri uri : uris) {
+ if (uri == null) throw new NullPointerException("Uri not " +
+ "allowed to be null");
+ String scheme = uri.getScheme();
+ if (scheme == null || (!scheme.equalsIgnoreCase("file") &&
+ !scheme.equalsIgnoreCase("content"))) {
+ throw new IllegalArgumentException("URI needs to have " +
+ "either scheme file or scheme content");
+ }
+ }
+ }
mNfcActivityManager.setNdefPushContentUri(activity, uris);
}
- // TODO javadoc
+ /**
+ * Set a callback that will dynamically generate one or more {@link Uri}s
+ * to send using Android Beam (TM). Every Uri the callback provides
+ * must have either scheme 'file' or scheme 'content'.
+ *
+ * <p>For the data provided through this callback, Android Beam tries to
+ * switch to alternate transports such as Bluetooth to achieve a fast
+ * transfer speed. Hence this method is very suitable
+ * for transferring large files such as pictures or songs.
+ *
+ * <p>The receiving side will store the content of each Uri in
+ * a file and present a notification to the user to open the file
+ * with a {@link android.content.Intent} with action
+ * {@link android.content.Intent#ACTION_VIEW}.
+ * If multiple URIs are sent, the {@link android.content.Intent} will refer
+ * to the first of the stored files.
+ *
+ * <p>This method may be called at any time before {@link Activity#onDestroy},
+ * but the URI(s) are only made available for Android Beam when the
+ * specified activity(s) are in resumed (foreground) state. The recommended
+ * approach is to call this method during your Activity's
+ * {@link Activity#onCreate} - see sample
+ * code below. This method does not immediately perform any I/O or blocking work,
+ * so is safe to call on your main thread.
+ *
+ * <p>{@link #setBeamPushUris} and {@link #setBeamPushUrisCallback}
+ * have priority over both {@link #setNdefPushMessage} and
+ * {@link #setNdefPushMessageCallback}.
+ *
+ * <p>If {@link #setBeamPushUris} is called with a null Uri array,
+ * and/or {@link #setBeamPushUrisCallback} is called with a null callback,
+ * then the Uri push will be completely disabled for the specified activity(s).
+ *
+ * <p>Code example:
+ * <pre>
+ * protected void onCreate(Bundle savedInstanceState) {
+ * super.onCreate(savedInstanceState);
+ * NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ * if (nfcAdapter == null) return; // NFC not available on this device
+ * nfcAdapter.setBeamPushUrisCallback(callback, this);
+ * }
+ * </pre>
+ * And that is it. Only one call per activity is necessary. The Android
+ * OS will automatically release its references to the Uri(s) and the
+ * Activity object when it is destroyed if you follow this pattern.
+ *
+ * <p class="note">Do not pass in an Activity that has already been through
+ * {@link Activity#onDestroy}. This is guaranteed if you call this API
+ * during {@link Activity#onCreate}.
+ *
+ * <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
+ *
+ * @param callback callback, or null to disable
+ * @param activity activity for which the Uri(s) will be pushed
+ */
public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
@@ -663,6 +784,10 @@ public final class NfcAdapter {
* {@link Activity#onDestroy}. This is guaranteed if you call this API
* during {@link Activity#onCreate}.
*
+ * <p class="note">For sending large content such as pictures and songs,
+ * consider using {@link #setBeamPushUris}, which switches to alternate transports
+ * such as Bluetooth to achieve a fast transfer rate.
+ *
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param message NDEF message to push over NFC, or null to disable
@@ -753,7 +878,9 @@ public final class NfcAdapter {
* <p class="note">Do not pass in an Activity that has already been through
* {@link Activity#onDestroy}. This is guaranteed if you call this API
* during {@link Activity#onCreate}.
- *
+ * <p class="note">For sending large content such as pictures and songs,
+ * consider using {@link #setBeamPushUris}, which switches to alternate transports
+ * such as Bluetooth to achieve a fast transfer rate.
* <p class="note">Requires the {@link android.Manifest.permission#NFC} permission.
*
* @param callback callback, or null to disable
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index f4abda6..ab64866 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -1360,7 +1360,14 @@ public interface IMountService extends IInterface {
*/
public Parcelable[] getVolumeList() throws RemoteException;
- public String getSecureContainerFilesystemPath(String id) throws RemoteException;
+ /**
+ * Gets the path on the filesystem for the ASEC container itself.
+ *
+ * @param cid ASEC container ID
+ * @return path to filesystem or {@code null} if it's not found
+ * @throws RemoteException
+ */
+ public String getSecureContainerFilesystemPath(String cid) throws RemoteException;
/*
* Fix permissions in a container which has just been created and populated.
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
index c59ed18..a643c8a 100644
--- a/core/java/android/preference/DialogPreference.java
+++ b/core/java/android/preference/DialogPreference.java
@@ -261,6 +261,8 @@ public abstract class DialogPreference extends Preference implements
@Override
protected void onClick() {
+ if (mDialog != null && mDialog.isShowing()) return;
+
showDialog(null);
}
diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index fd709f2..457e66c 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -115,6 +115,45 @@ public class RecognizerIntent {
public static final String ACTION_WEB_SEARCH = "android.speech.action.WEB_SEARCH";
/**
+ * Starts an activity that will prompt the user for speech without requiring the user's
+ * visual attention or touch input. It will send it through a speech recognizer,
+ * and either synthesize speech for a web search result or trigger
+ * another type of action based on the user's speech.
+ *
+ * This activity may be launched while device is locked in a secure mode.
+ * Special care must be taken to ensure that the voice actions that are performed while
+ * hands free cannot compromise the device's security.
+ * The activity should check the value of the {@link #EXTRA_SECURE} extra to determine
+ * whether the device has been securely locked. If so, the activity should either restrict
+ * the set of voice actions that are permitted or require some form of secure
+ * authentication before proceeding.
+ *
+ * To ensure that the activity's user interface is visible while the lock screen is showing,
+ * the activity should set the
+ * {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} window flag.
+ * Otherwise the activity's user interface may be hidden by the lock screen. The activity
+ * should take care not to leak private information when the device is securely locked.
+ *
+ * <p>Optional extras:
+ * <ul>
+ * <li>{@link #EXTRA_SECURE}
+ * </ul>
+ */
+ public static final String ACTION_VOICE_SEARCH_HANDS_FREE =
+ "android.speech.action.VOICE_SEARCH_HANDS_FREE";
+
+ /**
+ * Optional boolean to indicate that a "hands free" voice search was performed while the device
+ * was in a secure mode. An example of secure mode is when the device's screen lock is active,
+ * and it requires some form of authentication to be unlocked.
+ *
+ * When the device is securely locked, the voice search activity should either restrict
+ * the set of voice actions that are permitted, or require some form of secure authentication
+ * before proceeding.
+ */
+ public static final String EXTRA_SECURE = "android.speech.extras.EXTRA_SECURE";
+
+ /**
* The minimum length of an utterance. We will not stop recording before this amount of time.
*
* Note that it is extremely rare you'd want to specify this value in an intent. If you don't
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 62c267f..e4062e6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2255,9 +2255,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* flags, we would like a stable view of the content insets given to
* {@link #fitSystemWindows(Rect)}. This means that the insets seen there
* will always represent the worst case that the application can expect
- * as a continue state. In practice this means with any of system bar,
- * nav bar, and status bar shown, but not the space that would be needed
- * for an input method.
+ * as a continuous state. In the stock Android UI this is the space for
+ * the system bar, nav bar, and status bar, but not more transient elements
+ * such as an input method.
+ *
+ * The stable layout your UI sees is based on the system UI modes you can
+ * switch to. That is, if you specify {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}
+ * then you will get a stable layout for changes of the
+ * {@link #SYSTEM_UI_FLAG_FULLSCREEN} mode; if you specify
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, then you can transition
+ * to {@link #SYSTEM_UI_FLAG_FULLSCREEN} and {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}
+ * with a stable layout. (Note that you should avoid using
+ * {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION} by itself.)
+ *
+ * If you have set the window flag {@ WindowManager.LayoutParams#FLAG_FULLSCREEN}
+ * to hide the status bar (instead of using {@link #SYSTEM_UI_FLAG_FULLSCREEN}),
+ * then a hidden status bar will be considered a "stable" state for purposes
+ * here. This allows your UI to continually hide the status bar, while still
+ * using the system UI flags to hide the action bar while still retaining
+ * a stable layout. Note that changing the window fullscreen flag will never
+ * provide a stable layout for a clean transition.
*
* <p>If you are using ActionBar in
* overlay mode with {@link Window#FEATURE_ACTION_BAR_OVERLAY
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d62f513..d94275b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -691,13 +691,6 @@ public interface WindowManager extends ViewManager {
*/
public static final int FLAG_NEEDS_MENU_KEY = 0x08000000;
- /** Window flag: *sigh* The lock screen wants to continue running its
- * animation while it is fading. A kind-of hack to allow this. Maybe
- * in the future we just make this the default behavior.
- *
- * {@hide} */
- public static final int FLAG_KEEP_SURFACE_WHILE_ANIMATING = 0x10000000;
-
/** Window flag: special flag to limit the size of the window to be
* original size ([320x480] x density). Used to create window for applications
* running under compatibility mode.
diff --git a/core/java/android/webkit/AccessibilityInjector.java b/core/java/android/webkit/AccessibilityInjector.java
index 11bd815..cc490bd 100644
--- a/core/java/android/webkit/AccessibilityInjector.java
+++ b/core/java/android/webkit/AccessibilityInjector.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,484 +16,615 @@
package android.webkit;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.SystemClock;
import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.TextUtils.SimpleStringSplitter;
-import android.util.Log;
+import android.speech.tts.TextToSpeech;
import android.view.KeyEvent;
-import android.view.accessibility.AccessibilityEvent;
+import android.view.View;
import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.webkit.WebViewCore.EventHub;
-import java.util.ArrayList;
-import java.util.Stack;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
/**
- * This class injects accessibility into WebViews with disabled JavaScript or
- * WebViews with enabled JavaScript but for which we have no accessibility
- * script to inject.
- * </p>
- * Note: To avoid changes in the framework upon changing the available
- * navigation axis, or reordering the navigation axis, or changing
- * the key bindings, or defining sequence of actions to be bound to
- * a given key this class is navigation axis agnostic. It is only
- * aware of one navigation axis which is in fact the default behavior
- * of webViews while using the DPAD/TrackBall.
- * </p>
- * In general a key binding is a mapping from modifiers + key code to
- * a sequence of actions. For more detail how to specify key bindings refer to
- * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
- * </p>
- * The possible actions are invocations to
- * {@link #setCurrentAxis(int, boolean, String)}, or
- * {@link #traverseCurrentAxis(int, boolean, String)}
- * {@link #traverseGivenAxis(int, int, boolean, String)}
- * {@link #prefromAxisTransition(int, int, boolean, String)}
- * referred via the values of:
- * {@link #ACTION_SET_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
- * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
- * {@link #ACTION_PERFORM_AXIS_TRANSITION},
- * respectively.
- * The arguments for the action invocation are specified as offset
- * hexademical pairs. Note the last argument of the invocation
- * should NOT be specified in the binding as it is provided by
- * this class. For details about the key binding implementation
- * refer to {@link AccessibilityWebContentKeyBinding}.
+ * Handles injecting accessibility JavaScript and related JavaScript -> Java
+ * APIs.
*/
class AccessibilityInjector {
- private static final String LOG_TAG = "AccessibilityInjector";
+ // Default result returned from AndroidVox. Using true here means if the
+ // script fails, an accessibility service will always think that traversal
+ // has succeeded.
+ private static final String DEFAULT_ANDROIDVOX_RESULT = "true";
+
+ // The WebViewClassic this injector is responsible for managing.
+ private final WebViewClassic mWebViewClassic;
- private static final boolean DEBUG = true;
+ // Cached reference to mWebViewClassic.getContext(), for convenience.
+ private final Context mContext;
- private static final int ACTION_SET_CURRENT_AXIS = 0;
- private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
- private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
- private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
- private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
+ // Cached reference to mWebViewClassic.getWebView(), for convenience.
+ private final WebView mWebView;
- // the default WebView behavior abstracted as a navigation axis
- private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
+ // The Java objects that are exposed to JavaScript.
+ private TextToSpeech mTextToSpeech;
+ private CallbackHandler mCallback;
- // these are the same for all instances so make them process wide
- private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
- new ArrayList<AccessibilityWebContentKeyBinding>();
+ // Lazily loaded helper objects.
+ private AccessibilityManager mAccessibilityManager;
+ private AccessibilityInjectorFallback mAccessibilityInjectorFallback;
+ private JSONObject mAccessibilityJSONObject;
- // handle to the WebViewClassic this injector is associated with.
- private final WebViewClassic mWebView;
+ // Whether the accessibility script has been injected into the current page.
+ private boolean mAccessibilityScriptInjected;
- // events scheduled for sending as soon as we receive the selected text
- private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+ // Constants for determining script injection strategy.
+ private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
+ private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
+ @SuppressWarnings("unused")
+ private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
- // the current traversal axis
- private int mCurrentAxis = 2; // sentence
+ // Alias for TTS API exposed to JavaScript.
+ private static final String ALIAS_TTS_JS_INTERFACE = "accessibility";
- // we need to consume the up if we have handled the last down
- private boolean mLastDownEventHandled;
+ // Alias for traversal callback exposed to JavaScript.
+ private static final String ALIAS_TRAVERSAL_JS_INTERFACE = "accessibilityTraversal";
- // getting two empty selection strings in a row we let the WebView handle the event
- private boolean mIsLastSelectionStringNull;
+ // Template for JavaScript that injects a screen-reader.
+ private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
+ "javascript:(function() {" +
+ " var chooser = document.createElement('script');" +
+ " chooser.type = 'text/javascript';" +
+ " chooser.src = '%1s';" +
+ " document.getElementsByTagName('head')[0].appendChild(chooser);" +
+ " })();";
- // keep track of last direction
- private int mLastDirection;
+ // Template for JavaScript that performs AndroidVox actions.
+ private static final String ACCESSIBILITY_ANDROIDVOX_TEMPLATE =
+ "cvox.AndroidVox.performAction('%1s')";
/**
- * Creates a new injector associated with a given {@link WebViewClassic}.
+ * Creates an instance of the AccessibilityInjector based on
+ * {@code webViewClassic}.
*
- * @param webView The associated WebViewClassic.
+ * @param webViewClassic The WebViewClassic that this AccessibilityInjector
+ * manages.
*/
- public AccessibilityInjector(WebViewClassic webView) {
- mWebView = webView;
- ensureWebContentKeyBindings();
+ public AccessibilityInjector(WebViewClassic webViewClassic) {
+ mWebViewClassic = webViewClassic;
+ mWebView = webViewClassic.getWebView();
+ mContext = webViewClassic.getContext();
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
}
/**
- * Processes a key down <code>event</code>.
- *
- * @return True if the event was processed.
+ * Attempts to load scripting interfaces for accessibility.
+ * <p>
+ * This should be called when the window is attached.
+ * </p>
*/
- public boolean onKeyEvent(KeyEvent event) {
- // We do not handle ENTER in any circumstances.
- if (isEnterActionKey(event.getKeyCode())) {
- return false;
+ public void addAccessibilityApisIfNecessary() {
+ if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) {
+ return;
}
- if (event.getAction() == KeyEvent.ACTION_UP) {
- return mLastDownEventHandled;
+ addTtsApis();
+ addCallbackApis();
+ }
+
+ /**
+ * Attempts to unload scripting interfaces for accessibility.
+ * <p>
+ * This should be called when the window is detached.
+ * </p>
+ */
+ public void removeAccessibilityApisIfNecessary() {
+ removeTtsApis();
+ removeCallbackApis();
+ }
+
+ /**
+ * Initializes an {@link AccessibilityNodeInfo} with the actions and
+ * movement granularity levels supported by this
+ * {@link AccessibilityInjector}.
+ * <p>
+ * If an action identifier is added in this method, this
+ * {@link AccessibilityInjector} should also return {@code true} from
+ * {@link #supportsAccessibilityAction(int)}.
+ * </p>
+ *
+ * @param info The info to initialize.
+ * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
+ */
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
+ | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
+ info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
+ info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
+ info.addAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT);
+ info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT);
+ info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
+ info.setClickable(true);
+ }
+
+ /**
+ * Returns {@code true} if this {@link AccessibilityInjector} should handle
+ * the specified action.
+ *
+ * @param action An accessibility action identifier.
+ * @return {@code true} if this {@link AccessibilityInjector} should handle
+ * the specified action.
+ */
+ public boolean supportsAccessibilityAction(int action) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
+ case AccessibilityNodeInfo.ACTION_CLICK:
+ return true;
+ default:
+ return false;
}
+ }
- mLastDownEventHandled = false;
+ /**
+ * Performs the specified accessibility action.
+ *
+ * @param action The identifier of the action to perform.
+ * @param arguments The action arguments, or {@code null} if no arguments.
+ * @return {@code true} if the action was successful.
+ * @see View#performAccessibilityAction(int, Bundle)
+ */
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (!isAccessibilityEnabled()) {
+ mAccessibilityScriptInjected = false;
+ toggleFallbackAccessibilityInjector(false);
+ return false;
+ }
- AccessibilityWebContentKeyBinding binding = null;
- for (AccessibilityWebContentKeyBinding candidate : sBindings) {
- if (event.getKeyCode() == candidate.getKeyCode()
- && event.hasModifiers(candidate.getModifiers())) {
- binding = candidate;
- break;
- }
+ if (mAccessibilityScriptInjected) {
+ return sendActionToAndroidVox(action, arguments);
}
+
+ if (mAccessibilityInjectorFallback != null) {
+ return mAccessibilityInjectorFallback.performAccessibilityAction(action, arguments);
+ }
+
+ return false;
+ }
- if (binding == null) {
+ /**
+ * Attempts to handle key events when accessibility is turned on.
+ *
+ * @param event The key event to handle.
+ * @return {@code true} if the event was handled.
+ */
+ public boolean handleKeyEventIfNecessary(KeyEvent event) {
+ if (!isAccessibilityEnabled()) {
+ mAccessibilityScriptInjected = false;
+ toggleFallbackAccessibilityInjector(false);
return false;
}
- for (int i = 0, count = binding.getActionCount(); i < count; i++) {
- int actionCode = binding.getActionCode(i);
- String contentDescription = Integer.toHexString(binding.getAction(i));
- switch (actionCode) {
- case ACTION_SET_CURRENT_AXIS:
- int axis = binding.getFirstArgument(i);
- boolean sendEvent = (binding.getSecondArgument(i) == 1);
- setCurrentAxis(axis, sendEvent, contentDescription);
- mLastDownEventHandled = true;
- break;
- case ACTION_TRAVERSE_CURRENT_AXIS:
- int direction = binding.getFirstArgument(i);
- // on second null selection string in same direction - WebView handles the event
- if (direction == mLastDirection && mIsLastSelectionStringNull) {
- mIsLastSelectionStringNull = false;
- return false;
- }
- mLastDirection = direction;
- sendEvent = (binding.getSecondArgument(i) == 1);
- mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
- contentDescription);
- break;
- case ACTION_TRAVERSE_GIVEN_AXIS:
- direction = binding.getFirstArgument(i);
- // on second null selection string in same direction => WebView handle the event
- if (direction == mLastDirection && mIsLastSelectionStringNull) {
- mIsLastSelectionStringNull = false;
- return false;
- }
- mLastDirection = direction;
- axis = binding.getSecondArgument(i);
- sendEvent = (binding.getThirdArgument(i) == 1);
- traverseGivenAxis(direction, axis, sendEvent, contentDescription);
- mLastDownEventHandled = true;
- break;
- case ACTION_PERFORM_AXIS_TRANSITION:
- int fromAxis = binding.getFirstArgument(i);
- int toAxis = binding.getSecondArgument(i);
- sendEvent = (binding.getThirdArgument(i) == 1);
- prefromAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
- mLastDownEventHandled = true;
- break;
- case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
- // This is a special case since we treat the default WebView navigation
- // behavior as one of the possible navigation axis the user can use.
- // If we are not on the default WebView navigation axis this is NOP.
- if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
- // While WebVew handles navigation we do not get null selection
- // strings so do not check for that here as the cases above.
- mLastDirection = binding.getFirstArgument(i);
- sendEvent = (binding.getSecondArgument(i) == 1);
- traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
- sendEvent, contentDescription);
- mLastDownEventHandled = false;
- } else {
- mLastDownEventHandled = true;
- }
- break;
- default:
- Log.w(LOG_TAG, "Unknown action code: " + actionCode);
+ if (mAccessibilityScriptInjected) {
+ // if an accessibility script is injected we delegate to it the key
+ // handling. this script is a screen reader which is a fully fledged
+ // solution for blind users to navigate in and interact with web
+ // pages.
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
+ } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mWebViewClassic.sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
+ } else {
+ return false;
}
+
+ return true;
}
- return mLastDownEventHandled;
+ if (mAccessibilityInjectorFallback != null) {
+ // if an accessibility injector is present (no JavaScript enabled or
+ // the site opts out injecting our JavaScript screen reader) we let
+ // it decide whether to act on and consume the event.
+ return mAccessibilityInjectorFallback.onKeyEvent(event);
+ }
+
+ return false;
}
/**
- * Set the current navigation axis which will be used while
- * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+ * Attempts to handle selection change events when accessibility is using a
+ * non-JavaScript method.
*
- * @param axis The axis to set.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
+ * @param selectionString The selection string.
*/
- private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
- mCurrentAxis = axis;
- if (sendEvent) {
- AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent();
- event.getText().add(String.valueOf(axis));
- event.setContentDescription(contentDescription);
- sendAccessibilityEvent(event);
+ public void handleSelectionChangedIfNecessary(String selectionString) {
+ if (mAccessibilityInjectorFallback != null) {
+ mAccessibilityInjectorFallback.onSelectionStringChange(selectionString);
}
}
/**
- * Performs conditional transition one axis to another.
+ * Prepares for injecting accessibility scripts into a new page.
+ *
+ * @param url The URL that will be loaded.
+ */
+ public void onPageStarted(String url) {
+ mAccessibilityScriptInjected = false;
+ }
+
+ /**
+ * Attempts to inject the accessibility script using a {@code <script>} tag.
+ * <p>
+ * This should be called after a page has finished loading.
+ * </p>
*
- * @param fromAxis The axis which must be the current for the transition to occur.
- * @param toAxis The axis to which to transition.
- * @param sendEvent Flag if to send an event to announce successful transition.
- * @param contentDescription A description of the performed action.
+ * @param url The URL that just finished loading.
*/
- private void prefromAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
- String contentDescription) {
- if (mCurrentAxis == fromAxis) {
- setCurrentAxis(toAxis, sendEvent, contentDescription);
+ public void onPageFinished(String url) {
+ if (!isAccessibilityEnabled()) {
+ mAccessibilityScriptInjected = false;
+ toggleFallbackAccessibilityInjector(false);
+ return;
}
+
+ if (!shouldInjectJavaScript(url)) {
+ toggleFallbackAccessibilityInjector(true);
+ return;
+ }
+
+ toggleFallbackAccessibilityInjector(false);
+
+ final String injectionUrl = getScreenReaderInjectionUrl();
+ mWebView.loadUrl(injectionUrl);
+
+ mAccessibilityScriptInjected = true;
}
/**
- * Traverse the document along the current navigation axis.
+ * Toggles the non-JavaScript method for handling accessibility.
*
- * @param direction The direction of traversal.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- * @param contentDescription A description of the performed action.
- * @see #setCurrentAxis(int, boolean, String)
+ * @param enabled {@code true} to enable the non-JavaScript method, or
+ * {@code false} to disable it.
*/
- private boolean traverseCurrentAxis(int direction, boolean sendEvent,
- String contentDescription) {
- return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
+ private void toggleFallbackAccessibilityInjector(boolean enabled) {
+ if (enabled && (mAccessibilityInjectorFallback == null)) {
+ mAccessibilityInjectorFallback = new AccessibilityInjectorFallback(mWebViewClassic);
+ } else {
+ mAccessibilityInjectorFallback = null;
+ }
}
/**
- * Traverse the document along the given navigation axis.
+ * Determines whether it's okay to inject JavaScript into a given URL.
*
- * @param direction The direction of traversal.
- * @param axis The axis along which to traverse.
- * @param sendEvent Whether to send an accessibility event to
- * announce the change.
- * @param contentDescription A description of the performed action.
+ * @param url The URL to check.
+ * @return {@code true} if JavaScript should be injected, {@code false} if a
+ * non-JavaScript method should be used.
*/
- private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
- String contentDescription) {
- WebViewCore webViewCore = mWebView.getWebViewCore();
- if (webViewCore == null) {
+ private boolean shouldInjectJavaScript(String url) {
+ // Respect the WebView's JavaScript setting.
+ if (!isJavaScriptEnabled()) {
return false;
}
- AccessibilityEvent event = null;
- if (sendEvent) {
- event = getPartialyPopulatedAccessibilityEvent();
- // the text will be set upon receiving the selection string
- event.setContentDescription(contentDescription);
+ // Allow the page to opt out of Accessibility script injection.
+ if (getAxsUrlParameterValue(url) == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
+ return false;
}
- mScheduledEventStack.push(event);
- // if the axis is the default let WebView handle the event which will
- // result in cursor ring movement and selection of its content
- if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
+ // The user must explicitly enable Accessibility script injection.
+ if (!isScriptInjectionEnabled()) {
return false;
}
- webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
return true;
}
/**
- * Called when the <code>selectionString</code> has changed.
+ * @return {@code true} if the user has explicitly enabled Accessibility
+ * script injection.
*/
- public void onSelectionStringChange(String selectionString) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Selection string: " + selectionString);
- }
- mIsLastSelectionStringNull = (selectionString == null);
- if (mScheduledEventStack.isEmpty()) {
+ private boolean isScriptInjectionEnabled() {
+ final int injectionSetting = Settings.Secure.getInt(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0);
+ return (injectionSetting == 1);
+ }
+
+ /**
+ * Attempts to initialize and add interfaces for TTS, if that hasn't already
+ * been done.
+ */
+ private void addTtsApis() {
+ if (mTextToSpeech != null) {
return;
}
- AccessibilityEvent event = mScheduledEventStack.pop();
- if (event != null) {
- event.getText().add(selectionString);
- sendAccessibilityEvent(event);
- }
+
+ final String pkgName = mContext.getPackageName();
+
+ mTextToSpeech = new TextToSpeech(mContext, null, null, pkgName + ".**webview**", true);
+ mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE);
}
/**
- * Sends an {@link AccessibilityEvent}.
- *
- * @param event The event to send.
+ * Attempts to shutdown and remove interfaces for TTS, if that hasn't
+ * already been done.
*/
- private void sendAccessibilityEvent(AccessibilityEvent event) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Dispatching: " + event);
+ private void removeTtsApis() {
+ if (mTextToSpeech == null) {
+ return;
}
- // accessibility may be disabled while waiting for the selection string
- AccessibilityManager accessibilityManager =
- AccessibilityManager.getInstance(mWebView.getContext());
- if (accessibilityManager.isEnabled()) {
- accessibilityManager.sendAccessibilityEvent(event);
+
+ mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE);
+ mTextToSpeech.stop();
+ mTextToSpeech.shutdown();
+ mTextToSpeech = null;
+ }
+
+ private void addCallbackApis() {
+ if (mCallback != null) {
+ return;
}
+
+ mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE);
+ mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE);
}
- /**
- * @return An accessibility event whose members are populated except its
- * text and content description.
- */
- private AccessibilityEvent getPartialyPopulatedAccessibilityEvent() {
- AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SELECTED);
- event.setClassName(mWebView.getClass().getName());
- event.setPackageName(mWebView.getContext().getPackageName());
- event.setEnabled(mWebView.getWebView().isEnabled());
- return event;
+ private void removeCallbackApis() {
+ if (mCallback == null) {
+ return;
+ }
+
+ mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE);
+ mCallback = null;
}
/**
- * Ensures that the Web content key bindings are loaded.
+ * Returns the script injection preference requested by the URL, or
+ * {@link #ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED} if the page has no
+ * preference.
+ *
+ * @param url The URL to check.
+ * @return A script injection preference.
*/
- private void ensureWebContentKeyBindings() {
- if (sBindings.size() > 0) {
- return;
+ private int getAxsUrlParameterValue(String url) {
+ if (url == null) {
+ return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
}
- String webContentKeyBindingsString = Settings.Secure.getString(
- mWebView.getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+ try {
+ final List<NameValuePair> params = URLEncodedUtils.parse(new URI(url), null);
- SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
- semiColonSplitter.setString(webContentKeyBindingsString);
-
- while (semiColonSplitter.hasNext()) {
- String bindingString = semiColonSplitter.next();
- if (TextUtils.isEmpty(bindingString)) {
- Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
- + webContentKeyBindingsString);
- continue;
- }
- String[] keyValueArray = bindingString.split("=");
- if (keyValueArray.length != 2) {
- Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
- continue;
- }
- try {
- long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
- String[] actionStrings = keyValueArray[1].split(":");
- int[] actions = new int[actionStrings.length];
- for (int i = 0, count = actions.length; i < count; i++) {
- actions[i] = Integer.decode(actionStrings[i].trim());
+ for (NameValuePair param : params) {
+ if ("axs".equals(param.getName())) {
+ return verifyInjectionValue(param.getValue());
}
- sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
- } catch (NumberFormatException nfe) {
- Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
}
+ } catch (URISyntaxException e) {
+ // Do nothing.
}
+
+ return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
}
- private boolean isEnterActionKey(int keyCode) {
- return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
- || keyCode == KeyEvent.KEYCODE_ENTER
- || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
+ private int verifyInjectionValue(String value) {
+ try {
+ final int parsed = Integer.parseInt(value);
+
+ switch (parsed) {
+ case ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT:
+ return ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT;
+ case ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED:
+ return ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED;
+ }
+ } catch (NumberFormatException e) {
+ // Do nothing.
+ }
+
+ return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED;
}
/**
- * Represents a web content key-binding.
+ * @return The URL for injecting the screen reader.
*/
- private static final class AccessibilityWebContentKeyBinding {
+ private String getScreenReaderInjectionUrl() {
+ final String screenReaderUrl = Settings.Secure.getString(
+ mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
+ return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
+ }
- private static final int MODIFIERS_OFFSET = 32;
- private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
+ /**
+ * @return {@code true} if JavaScript is enabled in the {@link WebView}
+ * settings.
+ */
+ private boolean isJavaScriptEnabled() {
+ return mWebView.getSettings().getJavaScriptEnabled();
+ }
- private static final int KEY_CODE_OFFSET = 0;
- private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
+ /**
+ * @return {@code true} if accessibility is enabled.
+ */
+ private boolean isAccessibilityEnabled() {
+ return mAccessibilityManager.isEnabled();
+ }
- private static final int ACTION_OFFSET = 24;
- private static final int ACTION_MASK = 0xFF000000;
+ /**
+ * Packs an accessibility action into a JSON object and sends it to AndroidVox.
+ *
+ * @param action The action identifier.
+ * @param arguments The action arguments, if applicable.
+ * @return The result of the action.
+ */
+ private boolean sendActionToAndroidVox(int action, Bundle arguments) {
+ if (mAccessibilityJSONObject == null) {
+ mAccessibilityJSONObject = new JSONObject();
+ } else {
+ // Remove all keys from the object.
+ final Iterator<?> keys = mAccessibilityJSONObject.keys();
+ while (keys.hasNext()) {
+ keys.next();
+ keys.remove();
+ }
+ }
- private static final int FIRST_ARGUMENT_OFFSET = 16;
- private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
+ try {
+ mAccessibilityJSONObject.accumulate("action", action);
- private static final int SECOND_ARGUMENT_OFFSET = 8;
- private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ final int granularity = arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
+ mAccessibilityJSONObject.accumulate("granularity", granularity);
+ break;
+ case AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT:
+ final String element = arguments.getString(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING);
+ mAccessibilityJSONObject.accumulate("element", element);
+ break;
+ }
+ } catch (JSONException e) {
+ return false;
+ }
- private static final int THIRD_ARGUMENT_OFFSET = 0;
- private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
+ final String jsonString = mAccessibilityJSONObject.toString();
+ final String jsCode = String.format(ACCESSIBILITY_ANDROIDVOX_TEMPLATE, jsonString);
+ final String result = mCallback.performAction(mWebView, jsCode, DEFAULT_ANDROIDVOX_RESULT);
- private final long mKeyCodeAndModifiers;
+ return ("true".equalsIgnoreCase(result));
+ }
- private final int [] mActionSequence;
+ /**
+ * Exposes result interface to JavaScript.
+ */
+ private static class CallbackHandler {
+ private static final String JAVASCRIPT_ACTION_TEMPLATE =
+ "javascript:(function() { %s.onResult(%d, %s); })();";
- /**
- * @return The key code of the binding key.
- */
- public int getKeyCode() {
- return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
- }
+ // Time in milliseconds to wait for a result before failing.
+ private static final long RESULT_TIMEOUT = 200;
- /**
- * @return The meta state of the binding key.
- */
- public int getModifiers() {
- return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
- }
+ private final AtomicInteger mResultIdCounter = new AtomicInteger();
+ private final Object mResultLock = new Object();
+ private final String mInterfaceName;
- /**
- * @return The number of actions in the key binding.
- */
- public int getActionCount() {
- return mActionSequence.length;
- }
+ private String mResult = null;
+ private long mResultId = -1;
- /**
- * @param index The action for a given action <code>index</code>.
- */
- public int getAction(int index) {
- return mActionSequence[index];
+ private CallbackHandler(String interfaceName) {
+ mInterfaceName = interfaceName;
}
/**
- * @param index The action code for a given action <code>index</code>.
+ * Performs an action and attempts to wait for a result.
+ *
+ * @param webView The WebView to perform the action on.
+ * @param code JavaScript code that evaluates to a result.
+ * @param defaultResult The result to return if the action times out.
+ * @return The result of the action, or false if it timed out.
*/
- public int getActionCode(int index) {
- return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
+ private String performAction(WebView webView, String code, String defaultResult) {
+ final int resultId = mResultIdCounter.getAndIncrement();
+ final String url = String.format(
+ JAVASCRIPT_ACTION_TEMPLATE, mInterfaceName, resultId, code);
+ webView.loadUrl(url);
+
+ return getResultAndClear(resultId, defaultResult);
}
/**
- * @param index The first argument for a given action <code>index</code>.
+ * Gets the result of a request to perform an accessibility action.
+ *
+ * @param resultId The result id to match the result with the request.
+ * @param defaultResult The default result to return on timeout.
+ * @return The result of the request.
*/
- public int getFirstArgument(int index) {
- return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
+ private String getResultAndClear(int resultId, String defaultResult) {
+ synchronized (mResultLock) {
+ final boolean success = waitForResultTimedLocked(resultId);
+ final String result = success ? mResult : defaultResult;
+ clearResultLocked();
+ return result;
+ }
}
/**
- * @param index The second argument for a given action <code>index</code>.
+ * Clears the result state.
*/
- public int getSecondArgument(int index) {
- return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
+ private void clearResultLocked() {
+ mResultId = -1;
+ mResult = null;
}
/**
- * @param index The third argument for a given action <code>index</code>.
+ * Waits up to a given bound for a result of a request and returns it.
+ *
+ * @param resultId The result id to match the result with the request.
+ * @return Whether the result was received.
*/
- public int getThirdArgument(int index) {
- return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
+ private boolean waitForResultTimedLocked(int resultId) {
+ long waitTimeMillis = RESULT_TIMEOUT;
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ while (true) {
+ try {
+ if (mResultId == resultId) {
+ return true;
+ }
+ if (mResultId > resultId) {
+ return false;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ waitTimeMillis = RESULT_TIMEOUT - elapsedTimeMillis;
+ if (waitTimeMillis <= 0) {
+ return false;
+ }
+ mResultLock.wait(waitTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
}
/**
- * Creates a new instance.
- * @param keyCodeAndModifiers The key for the binding (key and modifiers).
- * @param actionSequence The sequence of action for the binding.
+ * Callback exposed to JavaScript. Handles returning the result of a
+ * request to a waiting (or potentially timed out) thread.
+ *
+ * @param id The result id of the request as a {@link String}.
+ * @param result The result of the request as a {@link String}.
*/
- public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
- mKeyCodeAndModifiers = keyCodeAndModifiers;
- mActionSequence = actionSequence;
- }
+ @SuppressWarnings("unused")
+ public void onResult(String id, String result) {
+ final long resultId;
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("modifiers: ");
- builder.append(getModifiers());
- builder.append(", keyCode: ");
- builder.append(getKeyCode());
- builder.append(", actions[");
- for (int i = 0, count = getActionCount(); i < count; i++) {
- builder.append("{actionCode");
- builder.append(i);
- builder.append(": ");
- builder.append(getActionCode(i));
- builder.append(", firstArgument: ");
- builder.append(getFirstArgument(i));
- builder.append(", secondArgument: ");
- builder.append(getSecondArgument(i));
- builder.append(", thirdArgument: ");
- builder.append(getThirdArgument(i));
- builder.append("}");
+ try {
+ resultId = Long.parseLong(id);
+ } catch (NumberFormatException e) {
+ return;
+ }
+
+ synchronized (mResultLock) {
+ if (resultId > mResultId) {
+ mResult = result;
+ mResultId = resultId;
+ }
+ mResultLock.notifyAll();
}
- builder.append("]");
- return builder.toString();
}
}
}
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
new file mode 100644
index 0000000..4d9c26c
--- /dev/null
+++ b/core/java/android/webkit/AccessibilityInjectorFallback.java
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit;
+
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.webkit.WebViewCore.EventHub;
+
+import java.util.ArrayList;
+import java.util.Stack;
+
+/**
+ * This class injects accessibility into WebViews with disabled JavaScript or
+ * WebViews with enabled JavaScript but for which we have no accessibility
+ * script to inject.
+ * </p>
+ * Note: To avoid changes in the framework upon changing the available
+ * navigation axis, or reordering the navigation axis, or changing
+ * the key bindings, or defining sequence of actions to be bound to
+ * a given key this class is navigation axis agnostic. It is only
+ * aware of one navigation axis which is in fact the default behavior
+ * of webViews while using the DPAD/TrackBall.
+ * </p>
+ * In general a key binding is a mapping from modifiers + key code to
+ * a sequence of actions. For more detail how to specify key bindings refer to
+ * {@link android.provider.Settings.Secure#ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS}.
+ * </p>
+ * The possible actions are invocations to
+ * {@link #setCurrentAxis(int, boolean, String)}, or
+ * {@link #traverseCurrentAxis(int, boolean, String)}
+ * {@link #traverseGivenAxis(int, int, boolean, String)}
+ * {@link #performAxisTransition(int, int, boolean, String)}
+ * referred via the values of:
+ * {@link #ACTION_SET_CURRENT_AXIS},
+ * {@link #ACTION_TRAVERSE_CURRENT_AXIS},
+ * {@link #ACTION_TRAVERSE_GIVEN_AXIS},
+ * {@link #ACTION_PERFORM_AXIS_TRANSITION},
+ * respectively.
+ * The arguments for the action invocation are specified as offset
+ * hexademical pairs. Note the last argument of the invocation
+ * should NOT be specified in the binding as it is provided by
+ * this class. For details about the key binding implementation
+ * refer to {@link AccessibilityWebContentKeyBinding}.
+ */
+class AccessibilityInjectorFallback {
+ private static final String LOG_TAG = "AccessibilityInjector";
+
+ private static final boolean DEBUG = true;
+
+ private static final int ACTION_SET_CURRENT_AXIS = 0;
+ private static final int ACTION_TRAVERSE_CURRENT_AXIS = 1;
+ private static final int ACTION_TRAVERSE_GIVEN_AXIS = 2;
+ private static final int ACTION_PERFORM_AXIS_TRANSITION = 3;
+ private static final int ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS = 4;
+
+ // WebView navigation axes from WebViewCore.h, plus an additional axis for
+ // the default behavior.
+ private static final int NAVIGATION_AXIS_CHARACTER = 0;
+ private static final int NAVIGATION_AXIS_WORD = 1;
+ private static final int NAVIGATION_AXIS_SENTENCE = 2;
+ @SuppressWarnings("unused")
+ private static final int NAVIGATION_AXIS_HEADING = 3;
+ private static final int NAVIGATION_AXIS_SIBLING = 5;
+ @SuppressWarnings("unused")
+ private static final int NAVIGATION_AXIS_PARENT_FIRST_CHILD = 5;
+ private static final int NAVIGATION_AXIS_DOCUMENT = 6;
+ private static final int NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR = 7;
+
+ // WebView navigation directions from WebViewCore.h.
+ private static final int NAVIGATION_DIRECTION_BACKWARD = 0;
+ private static final int NAVIGATION_DIRECTION_FORWARD = 1;
+
+ // these are the same for all instances so make them process wide
+ private static ArrayList<AccessibilityWebContentKeyBinding> sBindings =
+ new ArrayList<AccessibilityWebContentKeyBinding>();
+
+ // handle to the WebViewClassic this injector is associated with.
+ private final WebViewClassic mWebView;
+ private final WebView mWebViewInternal;
+
+ // events scheduled for sending as soon as we receive the selected text
+ private final Stack<AccessibilityEvent> mScheduledEventStack = new Stack<AccessibilityEvent>();
+
+ // the current traversal axis
+ private int mCurrentAxis = 2; // sentence
+
+ // we need to consume the up if we have handled the last down
+ private boolean mLastDownEventHandled;
+
+ // getting two empty selection strings in a row we let the WebView handle the event
+ private boolean mIsLastSelectionStringNull;
+
+ // keep track of last direction
+ private int mLastDirection;
+
+ /**
+ * Creates a new injector associated with a given {@link WebViewClassic}.
+ *
+ * @param webView The associated WebViewClassic.
+ */
+ public AccessibilityInjectorFallback(WebViewClassic webView) {
+ mWebView = webView;
+ mWebViewInternal = mWebView.getWebView();
+ ensureWebContentKeyBindings();
+ }
+
+ /**
+ * Processes a key down <code>event</code>.
+ *
+ * @return True if the event was processed.
+ */
+ public boolean onKeyEvent(KeyEvent event) {
+ // We do not handle ENTER in any circumstances.
+ if (isEnterActionKey(event.getKeyCode())) {
+ return false;
+ }
+
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ return mLastDownEventHandled;
+ }
+
+ mLastDownEventHandled = false;
+
+ AccessibilityWebContentKeyBinding binding = null;
+ for (AccessibilityWebContentKeyBinding candidate : sBindings) {
+ if (event.getKeyCode() == candidate.getKeyCode()
+ && event.hasModifiers(candidate.getModifiers())) {
+ binding = candidate;
+ break;
+ }
+ }
+
+ if (binding == null) {
+ return false;
+ }
+
+ for (int i = 0, count = binding.getActionCount(); i < count; i++) {
+ int actionCode = binding.getActionCode(i);
+ String contentDescription = Integer.toHexString(binding.getAction(i));
+ switch (actionCode) {
+ case ACTION_SET_CURRENT_AXIS:
+ int axis = binding.getFirstArgument(i);
+ boolean sendEvent = (binding.getSecondArgument(i) == 1);
+ setCurrentAxis(axis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ case ACTION_TRAVERSE_CURRENT_AXIS:
+ int direction = binding.getFirstArgument(i);
+ // on second null selection string in same direction - WebView handles the event
+ if (direction == mLastDirection && mIsLastSelectionStringNull) {
+ mIsLastSelectionStringNull = false;
+ return false;
+ }
+ mLastDirection = direction;
+ sendEvent = (binding.getSecondArgument(i) == 1);
+ mLastDownEventHandled = traverseCurrentAxis(direction, sendEvent,
+ contentDescription);
+ break;
+ case ACTION_TRAVERSE_GIVEN_AXIS:
+ direction = binding.getFirstArgument(i);
+ // on second null selection string in same direction => WebView handle the event
+ if (direction == mLastDirection && mIsLastSelectionStringNull) {
+ mIsLastSelectionStringNull = false;
+ return false;
+ }
+ mLastDirection = direction;
+ axis = binding.getSecondArgument(i);
+ sendEvent = (binding.getThirdArgument(i) == 1);
+ traverseGivenAxis(direction, axis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ case ACTION_PERFORM_AXIS_TRANSITION:
+ int fromAxis = binding.getFirstArgument(i);
+ int toAxis = binding.getSecondArgument(i);
+ sendEvent = (binding.getThirdArgument(i) == 1);
+ performAxisTransition(fromAxis, toAxis, sendEvent, contentDescription);
+ mLastDownEventHandled = true;
+ break;
+ case ACTION_TRAVERSE_DEFAULT_WEB_VIEW_BEHAVIOR_AXIS:
+ // This is a special case since we treat the default WebView navigation
+ // behavior as one of the possible navigation axis the user can use.
+ // If we are not on the default WebView navigation axis this is NOP.
+ if (mCurrentAxis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
+ // While WebVew handles navigation we do not get null selection
+ // strings so do not check for that here as the cases above.
+ mLastDirection = binding.getFirstArgument(i);
+ sendEvent = (binding.getSecondArgument(i) == 1);
+ traverseGivenAxis(mLastDirection, NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR,
+ sendEvent, contentDescription);
+ mLastDownEventHandled = false;
+ } else {
+ mLastDownEventHandled = true;
+ }
+ break;
+ default:
+ Log.w(LOG_TAG, "Unknown action code: " + actionCode);
+ }
+ }
+
+ return mLastDownEventHandled;
+ }
+
+ /**
+ * Set the current navigation axis which will be used while
+ * calling {@link #traverseCurrentAxis(int, boolean, String)}.
+ *
+ * @param axis The axis to set.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ */
+ private void setCurrentAxis(int axis, boolean sendEvent, String contentDescription) {
+ mCurrentAxis = axis;
+ if (sendEvent) {
+ final AccessibilityEvent event = getPartialyPopulatedAccessibilityEvent(
+ AccessibilityEvent.TYPE_ANNOUNCEMENT);
+ event.getText().add(String.valueOf(axis));
+ event.setContentDescription(contentDescription);
+ sendAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * Performs conditional transition one axis to another.
+ *
+ * @param fromAxis The axis which must be the current for the transition to occur.
+ * @param toAxis The axis to which to transition.
+ * @param sendEvent Flag if to send an event to announce successful transition.
+ * @param contentDescription A description of the performed action.
+ */
+ private void performAxisTransition(int fromAxis, int toAxis, boolean sendEvent,
+ String contentDescription) {
+ if (mCurrentAxis == fromAxis) {
+ setCurrentAxis(toAxis, sendEvent, contentDescription);
+ }
+ }
+
+ /**
+ * Traverse the document along the current navigation axis.
+ *
+ * @param direction The direction of traversal.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ * @param contentDescription A description of the performed action.
+ * @see #setCurrentAxis(int, boolean, String)
+ */
+ private boolean traverseCurrentAxis(int direction, boolean sendEvent,
+ String contentDescription) {
+ return traverseGivenAxis(direction, mCurrentAxis, sendEvent, contentDescription);
+ }
+
+ boolean performAccessibilityAction(int action, Bundle arguments) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ final int direction = getDirectionForAction(action);
+ final int axis = getAxisForGranularity(arguments.getInt(
+ AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT));
+ return traverseGivenAxis(direction, axis, true, null);
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Returns the {@link WebView}-defined direction for the given
+ * {@link AccessibilityNodeInfo}-defined action.
+ *
+ * @param action An accessibility action identifier.
+ * @return A web view navigation direction.
+ */
+ private static int getDirectionForAction(int action) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
+ return NAVIGATION_DIRECTION_FORWARD;
+ case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:
+ return NAVIGATION_DIRECTION_BACKWARD;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the {@link WebView}-defined axis for the given
+ * {@link AccessibilityNodeInfo}-defined granularity.
+ *
+ * @param granularity An accessibility granularity identifier.
+ * @return A web view navigation axis.
+ */
+ private static int getAxisForGranularity(int granularity) {
+ switch (granularity) {
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER:
+ return NAVIGATION_AXIS_CHARACTER;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD:
+ return NAVIGATION_AXIS_WORD;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE:
+ return NAVIGATION_AXIS_SENTENCE;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH:
+ // TODO: Figure out what nextSibling() actually means.
+ return NAVIGATION_AXIS_SIBLING;
+ case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE:
+ return NAVIGATION_AXIS_DOCUMENT;
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Traverse the document along the given navigation axis.
+ *
+ * @param direction The direction of traversal.
+ * @param axis The axis along which to traverse.
+ * @param sendEvent Whether to send an accessibility event to
+ * announce the change.
+ * @param contentDescription A description of the performed action.
+ */
+ private boolean traverseGivenAxis(int direction, int axis, boolean sendEvent,
+ String contentDescription) {
+ WebViewCore webViewCore = mWebView.getWebViewCore();
+ if (webViewCore == null) {
+ return false;
+ }
+
+ AccessibilityEvent event = null;
+ if (sendEvent) {
+ event = getPartialyPopulatedAccessibilityEvent(
+ AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY);
+ // the text will be set upon receiving the selection string
+ event.setContentDescription(contentDescription);
+ }
+ mScheduledEventStack.push(event);
+
+ // if the axis is the default let WebView handle the event which will
+ // result in cursor ring movement and selection of its content
+ if (axis == NAVIGATION_AXIS_DEFAULT_WEB_VIEW_BEHAVIOR) {
+ return false;
+ }
+
+ webViewCore.sendMessage(EventHub.MODIFY_SELECTION, direction, axis);
+ return true;
+ }
+
+ /**
+ * Called when the <code>selectionString</code> has changed.
+ */
+ public void onSelectionStringChange(String selectionString) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Selection string: " + selectionString);
+ }
+ mIsLastSelectionStringNull = (selectionString == null);
+ if (mScheduledEventStack.isEmpty()) {
+ return;
+ }
+ AccessibilityEvent event = mScheduledEventStack.pop();
+ if ((event != null) && (selectionString != null)) {
+ event.getText().add(selectionString);
+ event.setFromIndex(0);
+ event.setToIndex(selectionString.length());
+ sendAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * Sends an {@link AccessibilityEvent}.
+ *
+ * @param event The event to send.
+ */
+ private void sendAccessibilityEvent(AccessibilityEvent event) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Dispatching: " + event);
+ }
+ // accessibility may be disabled while waiting for the selection string
+ AccessibilityManager accessibilityManager =
+ AccessibilityManager.getInstance(mWebView.getContext());
+ if (accessibilityManager.isEnabled()) {
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+
+ /**
+ * @return An accessibility event whose members are populated except its
+ * text and content description.
+ */
+ private AccessibilityEvent getPartialyPopulatedAccessibilityEvent(int eventType) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+ mWebViewInternal.onInitializeAccessibilityEvent(event);
+ return event;
+ }
+
+ /**
+ * Ensures that the Web content key bindings are loaded.
+ */
+ private void ensureWebContentKeyBindings() {
+ if (sBindings.size() > 0) {
+ return;
+ }
+
+ String webContentKeyBindingsString = Settings.Secure.getString(
+ mWebView.getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_WEB_CONTENT_KEY_BINDINGS);
+
+ SimpleStringSplitter semiColonSplitter = new SimpleStringSplitter(';');
+ semiColonSplitter.setString(webContentKeyBindingsString);
+
+ while (semiColonSplitter.hasNext()) {
+ String bindingString = semiColonSplitter.next();
+ if (TextUtils.isEmpty(bindingString)) {
+ Log.e(LOG_TAG, "Disregarding malformed Web content key binding: "
+ + webContentKeyBindingsString);
+ continue;
+ }
+ String[] keyValueArray = bindingString.split("=");
+ if (keyValueArray.length != 2) {
+ Log.e(LOG_TAG, "Disregarding malformed Web content key binding: " + bindingString);
+ continue;
+ }
+ try {
+ long keyCodeAndModifiers = Long.decode(keyValueArray[0].trim());
+ String[] actionStrings = keyValueArray[1].split(":");
+ int[] actions = new int[actionStrings.length];
+ for (int i = 0, count = actions.length; i < count; i++) {
+ actions[i] = Integer.decode(actionStrings[i].trim());
+ }
+ sBindings.add(new AccessibilityWebContentKeyBinding(keyCodeAndModifiers, actions));
+ } catch (NumberFormatException nfe) {
+ Log.e(LOG_TAG, "Disregarding malformed key binding: " + bindingString);
+ }
+ }
+ }
+
+ private boolean isEnterActionKey(int keyCode) {
+ return keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+ || keyCode == KeyEvent.KEYCODE_ENTER
+ || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER;
+ }
+
+ /**
+ * Represents a web content key-binding.
+ */
+ private static final class AccessibilityWebContentKeyBinding {
+
+ private static final int MODIFIERS_OFFSET = 32;
+ private static final long MODIFIERS_MASK = 0xFFFFFFF00000000L;
+
+ private static final int KEY_CODE_OFFSET = 0;
+ private static final long KEY_CODE_MASK = 0x00000000FFFFFFFFL;
+
+ private static final int ACTION_OFFSET = 24;
+ private static final int ACTION_MASK = 0xFF000000;
+
+ private static final int FIRST_ARGUMENT_OFFSET = 16;
+ private static final int FIRST_ARGUMENT_MASK = 0x00FF0000;
+
+ private static final int SECOND_ARGUMENT_OFFSET = 8;
+ private static final int SECOND_ARGUMENT_MASK = 0x0000FF00;
+
+ private static final int THIRD_ARGUMENT_OFFSET = 0;
+ private static final int THIRD_ARGUMENT_MASK = 0x000000FF;
+
+ private final long mKeyCodeAndModifiers;
+
+ private final int [] mActionSequence;
+
+ /**
+ * @return The key code of the binding key.
+ */
+ public int getKeyCode() {
+ return (int) ((mKeyCodeAndModifiers & KEY_CODE_MASK) >> KEY_CODE_OFFSET);
+ }
+
+ /**
+ * @return The meta state of the binding key.
+ */
+ public int getModifiers() {
+ return (int) ((mKeyCodeAndModifiers & MODIFIERS_MASK) >> MODIFIERS_OFFSET);
+ }
+
+ /**
+ * @return The number of actions in the key binding.
+ */
+ public int getActionCount() {
+ return mActionSequence.length;
+ }
+
+ /**
+ * @param index The action for a given action <code>index</code>.
+ */
+ public int getAction(int index) {
+ return mActionSequence[index];
+ }
+
+ /**
+ * @param index The action code for a given action <code>index</code>.
+ */
+ public int getActionCode(int index) {
+ return (mActionSequence[index] & ACTION_MASK) >> ACTION_OFFSET;
+ }
+
+ /**
+ * @param index The first argument for a given action <code>index</code>.
+ */
+ public int getFirstArgument(int index) {
+ return (mActionSequence[index] & FIRST_ARGUMENT_MASK) >> FIRST_ARGUMENT_OFFSET;
+ }
+
+ /**
+ * @param index The second argument for a given action <code>index</code>.
+ */
+ public int getSecondArgument(int index) {
+ return (mActionSequence[index] & SECOND_ARGUMENT_MASK) >> SECOND_ARGUMENT_OFFSET;
+ }
+
+ /**
+ * @param index The third argument for a given action <code>index</code>.
+ */
+ public int getThirdArgument(int index) {
+ return (mActionSequence[index] & THIRD_ARGUMENT_MASK) >> THIRD_ARGUMENT_OFFSET;
+ }
+
+ /**
+ * Creates a new instance.
+ * @param keyCodeAndModifiers The key for the binding (key and modifiers).
+ * @param actionSequence The sequence of action for the binding.
+ */
+ public AccessibilityWebContentKeyBinding(long keyCodeAndModifiers, int[] actionSequence) {
+ mKeyCodeAndModifiers = keyCodeAndModifiers;
+ mActionSequence = actionSequence;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("modifiers: ");
+ builder.append(getModifiers());
+ builder.append(", keyCode: ");
+ builder.append(getKeyCode());
+ builder.append(", actions[");
+ for (int i = 0, count = getActionCount(); i < count; i++) {
+ builder.append("{actionCode");
+ builder.append(i);
+ builder.append(": ");
+ builder.append(getActionCode(i));
+ builder.append(", firstArgument: ");
+ builder.append(getFirstArgument(i));
+ builder.append(", secondArgument: ");
+ builder.append(getSecondArgument(i));
+ builder.append(", thirdArgument: ");
+ builder.append(getThirdArgument(i));
+ builder.append("}");
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+ }
+}
diff --git a/core/java/android/webkit/WebCoreThreadWatchdog.java b/core/java/android/webkit/WebCoreThreadWatchdog.java
index 655db31..a22e6e8 100644
--- a/core/java/android/webkit/WebCoreThreadWatchdog.java
+++ b/core/java/android/webkit/WebCoreThreadWatchdog.java
@@ -26,6 +26,10 @@ import android.os.Message;
import android.os.Process;
import android.webkit.WebViewCore.EventHub;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
// A Runnable that will monitor if the WebCore thread is still
// processing messages by pinging it every so often. It is safe
// to call the public methods of this class from any thread.
@@ -51,25 +55,31 @@ class WebCoreThreadWatchdog implements Runnable {
// After the first timeout, use a shorter period before re-prompting the user.
private static final int SUBSEQUENT_TIMEOUT_PERIOD = 15 * 1000;
- private Context mContext;
private Handler mWebCoreThreadHandler;
private Handler mHandler;
private boolean mPaused;
+ private Set<WebViewClassic> mWebViews;
+
private static WebCoreThreadWatchdog sInstance;
- public synchronized static WebCoreThreadWatchdog start(Context context,
- Handler webCoreThreadHandler) {
+ public synchronized static WebCoreThreadWatchdog start(Handler webCoreThreadHandler) {
if (sInstance == null) {
- sInstance = new WebCoreThreadWatchdog(context, webCoreThreadHandler);
+ sInstance = new WebCoreThreadWatchdog(webCoreThreadHandler);
new Thread(sInstance, "WebCoreThreadWatchdog").start();
}
return sInstance;
}
- public synchronized static void updateContext(Context context) {
+ public synchronized static void registerWebView(WebViewClassic w) {
if (sInstance != null) {
- sInstance.setContext(context);
+ sInstance.addWebView(w);
+ }
+ }
+
+ public synchronized static void unregisterWebView(WebViewClassic w) {
+ if (sInstance != null) {
+ sInstance.removeWebView(w);
}
}
@@ -85,12 +95,18 @@ class WebCoreThreadWatchdog implements Runnable {
}
}
- private void setContext(Context context) {
- mContext = context;
+ private void addWebView(WebViewClassic w) {
+ if (mWebViews == null) {
+ mWebViews = new HashSet<WebViewClassic>();
+ }
+ mWebViews.add(w);
}
- private WebCoreThreadWatchdog(Context context, Handler webCoreThreadHandler) {
- mContext = context;
+ private void removeWebView(WebViewClassic w) {
+ mWebViews.remove(w);
+ }
+
+ private WebCoreThreadWatchdog(Handler webCoreThreadHandler) {
mWebCoreThreadHandler = webCoreThreadHandler;
}
@@ -147,39 +163,41 @@ class WebCoreThreadWatchdog implements Runnable {
break;
case TIMED_OUT:
- if ((mContext == null) || !(mContext instanceof Activity)) return;
- new AlertDialog.Builder(mContext)
- .setMessage(com.android.internal.R.string.webpage_unresponsive)
- .setPositiveButton(com.android.internal.R.string.force_close,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // User chose to force close.
- Process.killProcess(Process.myPid());
- }
- })
- .setNegativeButton(com.android.internal.R.string.wait,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The user chose to wait. The last HEARTBEAT message
- // will still be in the WebCore thread's queue, so all
- // we need to do is post another TIMED_OUT so that the
- // user will get prompted again if the WebCore thread
- // doesn't sort itself out.
- sendMessageDelayed(obtainMessage(TIMED_OUT),
- SUBSEQUENT_TIMEOUT_PERIOD);
- }
- })
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- sendMessageDelayed(obtainMessage(TIMED_OUT),
- SUBSEQUENT_TIMEOUT_PERIOD);
+ boolean postedDialog = false;
+ synchronized (WebCoreThreadWatchdog.class) {
+ Iterator<WebViewClassic> it = mWebViews.iterator();
+ // Check each WebView we are aware of and find one that is capable of
+ // showing the user a prompt dialog.
+ while (it.hasNext()) {
+ WebView activeView = it.next().getWebView();
+
+ if (activeView.getWindowToken() != null &&
+ activeView.getViewRootImpl() != null) {
+ postedDialog = activeView.post(new PageNotRespondingRunnable(
+ activeView.getContext(), this));
+
+ if (postedDialog) {
+ // We placed the message into the UI thread for an attached
+ // WebView so we've made our best attempt to display the
+ // "page not responding" dialog to the user. Although the
+ // message is in the queue, there is no guarantee when/if
+ // the runnable will execute. In the case that the runnable
+ // never executes, the user will need to terminate the
+ // process manually.
+ break;
}
- })
- .setIcon(android.R.drawable.ic_dialog_alert)
- .show();
+ }
+ }
+
+ if (!postedDialog) {
+ // There's no active webview we can use to show the dialog, so
+ // wait again. If we never get a usable view, the user will
+ // never get the chance to terminate the process, and will
+ // need to do it manually.
+ sendMessageDelayed(obtainMessage(TIMED_OUT),
+ SUBSEQUENT_TIMEOUT_PERIOD);
+ }
+ }
break;
}
}
@@ -205,4 +223,55 @@ class WebCoreThreadWatchdog implements Runnable {
Looper.loop();
}
+
+ private class PageNotRespondingRunnable implements Runnable {
+ Context mContext;
+ private Handler mWatchdogHandler;
+
+ public PageNotRespondingRunnable(Context context, Handler watchdogHandler) {
+ mContext = context;
+ mWatchdogHandler = watchdogHandler;
+ }
+
+ @Override
+ public void run() {
+ // This must run on the UI thread as it is displaying an AlertDialog.
+ assert Looper.getMainLooper().getThread() == Thread.currentThread();
+ new AlertDialog.Builder(mContext)
+ .setMessage(com.android.internal.R.string.webpage_unresponsive)
+ .setPositiveButton(com.android.internal.R.string.force_close,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // User chose to force close.
+ Process.killProcess(Process.myPid());
+ }
+ })
+ .setNegativeButton(com.android.internal.R.string.wait,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // The user chose to wait. The last HEARTBEAT message
+ // will still be in the WebCore thread's queue, so all
+ // we need to do is post another TIMED_OUT so that the
+ // user will get prompted again if the WebCore thread
+ // doesn't sort itself out.
+ mWatchdogHandler.sendMessageDelayed(
+ mWatchdogHandler.obtainMessage(TIMED_OUT),
+ SUBSEQUENT_TIMEOUT_PERIOD);
+ }
+ })
+ .setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mWatchdogHandler.sendMessageDelayed(
+ mWatchdogHandler.obtainMessage(TIMED_OUT),
+ SUBSEQUENT_TIMEOUT_PERIOD);
+ }
+ })
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .show();
+ }
+ }
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index ba5a417..cbb3011 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1686,6 +1686,10 @@ public class WebView extends AbsoluteLayout
WebView.super.computeScroll();
}
+ public boolean super_performAccessibilityAction(int action, Bundle arguments) {
+ return WebView.super.performAccessibilityAction(action, arguments);
+ }
+
public boolean super_performLongClick() {
return WebView.super.performLongClick();
}
@@ -1938,6 +1942,11 @@ public class WebView extends AbsoluteLayout
mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
}
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
+ }
+
/** @hide */
@Override
protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 7786564..7f43552 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -60,9 +60,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
-import android.provider.Settings;
import android.security.KeyChain;
-import android.speech.tts.TextToSpeech;
import android.text.Editable;
import android.text.InputType;
import android.text.Selection;
@@ -102,7 +100,6 @@ import android.webkit.WebViewCore.DrawData;
import android.webkit.WebViewCore.EventHub;
import android.webkit.WebViewCore.TextFieldInitData;
import android.webkit.WebViewCore.TextSelectionData;
-import android.webkit.WebViewCore.TouchHighlightData;
import android.webkit.WebViewCore.WebKitHitTest;
import android.widget.AbsoluteLayout;
import android.widget.Adapter;
@@ -276,6 +273,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
newCursorPosition -= text.length() - limitedText.length();
}
super.setComposingText(limitedText, newCursorPosition);
+ updateSelection();
if (limitedText != text) {
restartInput();
int lastCaret = start + limitedText.length();
@@ -288,19 +286,44 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
@Override
public boolean commitText(CharSequence text, int newCursorPosition) {
setComposingText(text, newCursorPosition);
- int cursorPosition = Selection.getSelectionEnd(getEditable());
- setComposingRegion(cursorPosition, cursorPosition);
+ finishComposingText();
return true;
}
@Override
public boolean deleteSurroundingText(int leftLength, int rightLength) {
- Editable editable = getEditable();
- int cursorPosition = Selection.getSelectionEnd(editable);
- int startDelete = Math.max(0, cursorPosition - leftLength);
- int endDelete = Math.min(editable.length(),
- cursorPosition + rightLength);
- setNewText(startDelete, endDelete, "");
+ // This code is from BaseInputConnection#deleteSurroundText.
+ // We have to delete the same text in webkit.
+ Editable content = getEditable();
+ int a = Selection.getSelectionStart(content);
+ int b = Selection.getSelectionEnd(content);
+
+ if (a > b) {
+ int tmp = a;
+ a = b;
+ b = tmp;
+ }
+
+ int ca = getComposingSpanStart(content);
+ int cb = getComposingSpanEnd(content);
+ if (cb < ca) {
+ int tmp = ca;
+ ca = cb;
+ cb = tmp;
+ }
+ if (ca != -1 && cb != -1) {
+ if (ca < a) a = ca;
+ if (cb > b) b = cb;
+ }
+
+ int endDelete = Math.min(content.length(), b + rightLength);
+ if (endDelete > b) {
+ setNewText(b, endDelete, "");
+ }
+ int startDelete = Math.max(0, a - leftLength);
+ if (startDelete < a) {
+ setNewText(startDelete, a, "");
+ }
return super.deleteSurroundingText(leftLength, rightLength);
}
@@ -413,6 +436,46 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
outAttrs.imeOptions = mImeOptions;
outAttrs.hintText = mHint;
outAttrs.initialCapsMode = getCursorCapsMode(InputType.TYPE_CLASS_TEXT);
+
+ Editable editable = getEditable();
+ int selectionStart = Selection.getSelectionStart(editable);
+ int selectionEnd = Selection.getSelectionEnd(editable);
+ if (selectionStart < 0 || selectionEnd < 0) {
+ selectionStart = editable.length();
+ selectionEnd = selectionStart;
+ }
+ outAttrs.initialSelStart = selectionStart;
+ outAttrs.initialSelEnd = selectionEnd;
+ }
+
+ @Override
+ public boolean setSelection(int start, int end) {
+ boolean result = super.setSelection(start, end);
+ updateSelection();
+ return result;
+ }
+
+ @Override
+ public boolean setComposingRegion(int start, int end) {
+ boolean result = super.setComposingRegion(start, end);
+ updateSelection();
+ return result;
+ }
+
+ /**
+ * Send the selection and composing spans to the IME.
+ */
+ private void updateSelection() {
+ Editable editable = getEditable();
+ int selectionStart = Selection.getSelectionStart(editable);
+ int selectionEnd = Selection.getSelectionEnd(editable);
+ int composingStart = getComposingSpanStart(editable);
+ int composingEnd = getComposingSpanEnd(editable);
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.updateSelection(mWebView, selectionStart, selectionEnd,
+ composingStart, composingEnd);
+ }
}
/**
@@ -431,14 +494,18 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
boolean isCharacterDelete = false;
int textLength = text.length();
int originalLength = original.length();
- if (textLength > originalLength) {
- isCharacterAdd = (textLength == originalLength + 1)
- && TextUtils.regionMatches(text, 0, original, 0,
- originalLength);
- } else if (originalLength > textLength) {
- isCharacterDelete = (textLength == originalLength - 1)
- && TextUtils.regionMatches(text, 0, original, 0,
- textLength);
+ int selectionStart = Selection.getSelectionStart(editable);
+ int selectionEnd = Selection.getSelectionEnd(editable);
+ if (selectionStart == selectionEnd) {
+ if (textLength > originalLength) {
+ isCharacterAdd = (textLength == originalLength + 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ originalLength);
+ } else if (originalLength > textLength) {
+ isCharacterDelete = (textLength == originalLength - 1)
+ && TextUtils.regionMatches(text, 0, original, 0,
+ textLength);
+ }
}
if (isCharacterAdd) {
sendCharacter(text.charAt(textLength - 1));
@@ -867,15 +934,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private static final int MOTIONLESS_IGNORE = 3;
private int mHeldMotionless;
- // An instance for injecting accessibility in WebViews with disabled
- // JavaScript or ones for which no accessibility script exists
+ // Lazily-instantiated instance for injecting accessibility.
private AccessibilityInjector mAccessibilityInjector;
- // flag indicating if accessibility script is injected so we
- // know to handle Shift and arrows natively first
- private boolean mAccessibilityScriptInjected;
-
-
/**
* How long the caret handle will last without being touched.
*/
@@ -1084,34 +1145,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private int mHorizontalScrollBarMode = SCROLLBAR_AUTO;
private int mVerticalScrollBarMode = SCROLLBAR_AUTO;
- // constants for determining script injection strategy
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1;
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0;
- private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1;
-
- // the alias via which accessibility JavaScript interface is exposed
- private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility";
-
- // Template for JavaScript that injects a screen-reader.
- private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE =
- "javascript:(function() {" +
- " var chooser = document.createElement('script');" +
- " chooser.type = 'text/javascript';" +
- " chooser.src = '%1s';" +
- " document.getElementsByTagName('head')[0].appendChild(chooser);" +
- " })();";
-
- // Regular expression that matches the "axs" URL parameter.
- // The value of 0 means the accessibility script is opted out
- // The value of 1 means the accessibility script is already injected
- private static final String PATTERN_MATCH_AXS_URL_PARAMETER = "(\\?axs=(0|1))|(&axs=(0|1))";
-
- // TextToSpeech instance exposed to JavaScript to the injected screenreader.
- private TextToSpeech mTextToSpeech;
-
- // variable to cache the above pattern in case accessibility is enabled.
- private Pattern mMatchAxsUrlParameterPattern;
-
/**
* Max distance to overscroll by in pixels.
* This how far content can be pulled beyond its normal bounds by the user.
@@ -1630,43 +1663,66 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return true;
}
- /**
- * Adds accessibility APIs to JavaScript.
- *
- * Note: This method is responsible to performing the necessary
- * check if the accessibility APIs should be exposed.
- */
- private void addAccessibilityApisToJavaScript() {
- if (AccessibilityManager.getInstance(mContext).isEnabled()
- && getSettings().getJavaScriptEnabled()) {
- // exposing the TTS for now ...
- final Context ctx = mContext;
- if (ctx != null) {
- final String packageName = ctx.getPackageName();
- if (packageName != null) {
- mTextToSpeech = new TextToSpeech(ctx, null, null,
- packageName + ".**webview**", true);
- addJavascriptInterface(mTextToSpeech, ALIAS_ACCESSIBILITY_JS_INTERFACE);
+ @Override
+ public boolean performAccessibilityAction(int action, Bundle arguments) {
+ if (!mWebView.isEnabled()) {
+ // Only default actions are supported while disabled.
+ return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
+ }
+
+ if (mAccessibilityInjector.supportsAccessibilityAction(action)) {
+ return mAccessibilityInjector.performAccessibilityAction(action, arguments);
+ }
+
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
+ case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
+ final int convertedContentHeight = contentToViewY(getContentHeight());
+ final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
+ - mWebView.getPaddingBottom();
+ final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
+ final boolean canScrollBackward = (getScrollY() > 0);
+ final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
+ if ((action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) && canScrollBackward) {
+ mWebView.scrollBy(0, adjustedViewHeight);
+ return true;
+ }
+ if ((action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) && canScrollForward) {
+ mWebView.scrollBy(0, -adjustedViewHeight);
+ return true;
}
+ return false;
}
}
- }
- /**
- * Removes accessibility APIs from JavaScript.
- */
- private void removeAccessibilityApisFromJavaScript() {
- // exposing the TTS for now ...
- if (mTextToSpeech != null) {
- removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE);
- mTextToSpeech.shutdown();
- mTextToSpeech = null;
- }
+ return mWebViewPrivate.super_performAccessibilityAction(action, arguments);
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ if (!mWebView.isEnabled()) {
+ // Only default actions are supported while disabled.
+ return;
+ }
+
info.setScrollable(isScrollableForAccessibility());
+
+ final int convertedContentHeight = contentToViewY(getContentHeight());
+ final int adjustedViewHeight = getHeight() - mWebView.getPaddingTop()
+ - mWebView.getPaddingBottom();
+ final int maxScrollY = Math.max(convertedContentHeight - adjustedViewHeight, 0);
+ final boolean canScrollBackward = (getScrollY() > 0);
+ final boolean canScrollForward = ((getScrollY() - maxScrollY) > 0);
+
+ if (canScrollForward) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
+ }
+
+ if (canScrollForward) {
+ info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
+ }
+
+ mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info);
}
@Override
@@ -1684,6 +1740,17 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
event.setMaxScrollY(Math.max(convertedContentHeight - adjustedViewHeight, 0));
}
+ private boolean isAccessibilityEnabled() {
+ return AccessibilityManager.getInstance(mContext).isEnabled();
+ }
+
+ private AccessibilityInjector getAccessibilityInjector() {
+ if (mAccessibilityInjector == null) {
+ mAccessibilityInjector = new AccessibilityInjector(this);
+ }
+ return mAccessibilityInjector;
+ }
+
private boolean isScrollableForAccessibility() {
return (contentToViewX(getContentWidth()) > getWidth() - mWebView.getPaddingLeft()
- mWebView.getPaddingRight()
@@ -3365,11 +3432,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
nativeSetPauseDrawing(mNativeClass, false);
}
}
- // Ensure that the watchdog has a currently valid Context to be able to display
- // a prompt dialog. For example, if the Activity was finished whilst the WebCore
- // thread was blocked and the Activity is started again, we may reuse the blocked
- // thread, but we'll have a new Activity.
- WebCoreThreadWatchdog.updateContext(mContext);
// We get a call to onResume for new WebViews (i.e. mIsPaused will be false). We need
// to ensure that the Watchdog thread is running for the new WebView, so call
// it outside the if block above.
@@ -3824,7 +3886,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
// reset the flag since we set to true in if need after
// loading is see onPageFinished(Url)
- mAccessibilityScriptInjected = false;
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector().onPageStarted(url);
+ }
// Don't start out editing.
mIsEditingText = false;
@@ -3836,114 +3900,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
*/
/* package */ void onPageFinished(String url) {
mZoomManager.onPageFinished(url);
- injectAccessibilityForUrl(url);
- }
-
- /**
- * This method injects accessibility in the loaded document if accessibility
- * is enabled. If JavaScript is enabled we try to inject a URL specific script.
- * If no URL specific script is found or JavaScript is disabled we fallback to
- * the default {@link AccessibilityInjector} implementation.
- * </p>
- * If the URL has the "axs" paramter set to 1 it has already done the
- * script injection so we do nothing. If the parameter is set to 0
- * the URL opts out accessibility script injection so we fall back to
- * the default {@link AccessibilityInjector}.
- * </p>
- * Note: If the user has not opted-in the accessibility script injection no scripts
- * are injected rather the default {@link AccessibilityInjector} implementation
- * is used.
- *
- * @param url The URL loaded by this {@link WebView}.
- */
- private void injectAccessibilityForUrl(String url) {
- if (mWebViewCore == null) {
- return;
- }
- AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
-
- if (!accessibilityManager.isEnabled()) {
- // it is possible that accessibility was turned off between reloads
- ensureAccessibilityScriptInjectorInstance(false);
- return;
- }
-
- if (!getSettings().getJavaScriptEnabled()) {
- // no JS so we fallback to the basic buil-in support
- ensureAccessibilityScriptInjectorInstance(true);
- return;
- }
-
- // check the URL "axs" parameter to choose appropriate action
- int axsParameterValue = getAxsUrlParameterValue(url);
- if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) {
- boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt(mContext
- .getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
- if (onDeviceScriptInjectionEnabled) {
- ensureAccessibilityScriptInjectorInstance(false);
- // neither script injected nor script injection opted out => we inject
- mWebView.loadUrl(getScreenReaderInjectingJs());
- // TODO: Set this flag after successfull script injection. Maybe upon injection
- // the chooser should update the meta tag and we check it to declare success
- mAccessibilityScriptInjected = true;
- } else {
- // injection disabled so we fallback to the basic built-in support
- ensureAccessibilityScriptInjectorInstance(true);
- }
- } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT) {
- // injection opted out so we fallback to the basic buil-in support
- ensureAccessibilityScriptInjectorInstance(true);
- } else if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED) {
- ensureAccessibilityScriptInjectorInstance(false);
- // the URL provides accessibility but we still need to add our generic script
- mWebView.loadUrl(getScreenReaderInjectingJs());
- } else {
- Log.e(LOGTAG, "Unknown URL value for the \"axs\" URL parameter: " + axsParameterValue);
- }
- }
-
- /**
- * Ensures the instance of the {@link AccessibilityInjector} to be present ot not.
- *
- * @param present True to ensure an insance, false to ensure no instance.
- */
- private void ensureAccessibilityScriptInjectorInstance(boolean present) {
- if (present) {
- if (mAccessibilityInjector == null) {
- mAccessibilityInjector = new AccessibilityInjector(this);
- }
- } else {
- mAccessibilityInjector = null;
- }
- }
-
- /**
- * Gets JavaScript that injects a screen-reader.
- *
- * @return The JavaScript snippet.
- */
- private String getScreenReaderInjectingJs() {
- String screenReaderUrl = Settings.Secure.getString(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SCREEN_READER_URL);
- return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, screenReaderUrl);
- }
- /**
- * Gets the "axs" URL parameter value.
- *
- * @param url A url to fetch the paramter from.
- * @return The parameter value if such, -1 otherwise.
- */
- private int getAxsUrlParameterValue(String url) {
- if (mMatchAxsUrlParameterPattern == null) {
- mMatchAxsUrlParameterPattern = Pattern.compile(PATTERN_MATCH_AXS_URL_PARAMETER);
- }
- Matcher matcher = mMatchAxsUrlParameterPattern.matcher(url);
- if (matcher.find()) {
- String keyValuePair = url.substring(matcher.start(), matcher.end());
- return Integer.parseInt(keyValuePair.split("=")[1]);
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector().onPageFinished(url);
}
- return -1;
}
// scale from content to view coordinates, and pin
@@ -4901,30 +4861,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return false;
}
- // accessibility support
- if (accessibilityScriptInjected()) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- // if an accessibility script is injected we delegate to it the key handling.
- // this script is a screen reader which is a fully fledged solution for blind
- // users to navigate in and interact with web pages.
- sendBatchableInputMessage(EventHub.KEY_DOWN, 0, 0, event);
- return true;
- } else {
- // Clean up if accessibility was disabled after loading the current URL.
- mAccessibilityScriptInjected = false;
- }
- } else if (mAccessibilityInjector != null) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- if (mAccessibilityInjector.onKeyEvent(event)) {
- // if an accessibility injector is present (no JavaScript enabled or the site
- // opts out injecting our JavaScript screen reader) we let it decide whether
- // to act on and consume the event.
- return true;
- }
- } else {
- // Clean up if accessibility was disabled after loading the current URL.
- mAccessibilityInjector = null;
- }
+ // See if the accessibility injector needs to handle this event.
+ if (isAccessibilityEnabled()
+ && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
+ return true;
}
if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
@@ -5028,30 +4968,10 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
return false;
}
- // accessibility support
- if (accessibilityScriptInjected()) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- // if an accessibility script is injected we delegate to it the key handling.
- // this script is a screen reader which is a fully fledged solution for blind
- // users to navigate in and interact with web pages.
- sendBatchableInputMessage(EventHub.KEY_UP, 0, 0, event);
- return true;
- } else {
- // Clean up if accessibility was disabled after loading the current URL.
- mAccessibilityScriptInjected = false;
- }
- } else if (mAccessibilityInjector != null) {
- if (AccessibilityManager.getInstance(mContext).isEnabled()) {
- if (mAccessibilityInjector.onKeyEvent(event)) {
- // if an accessibility injector is present (no JavaScript enabled or the site
- // opts out injecting our JavaScript screen reader) we let it decide whether to
- // act on and consume the event.
- return true;
- }
- } else {
- // Clean up if accessibility was disabled after loading the current URL.
- mAccessibilityInjector = null;
- }
+ // See if the accessibility injector needs to handle this event.
+ if (isAccessibilityEnabled()
+ && getAccessibilityInjector().handleKeyEventIfNecessary(event)) {
+ return true;
}
if (isEnterActionKey(keyCode)) {
@@ -5167,6 +5087,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
private void adjustSelectionCursors() {
if (mIsCaretSelection) {
+ syncSelectionCursors();
return; // no need to swap left and right handles.
}
@@ -5352,7 +5273,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
public void onAttachedToWindow() {
if (mWebView.hasWindowFocus()) setActive(true);
- addAccessibilityApisToJavaScript();
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector().addAccessibilityApisIfNecessary();
+ }
updateHwAccelerated();
}
@@ -5363,7 +5286,14 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mZoomManager.dismissZoomPicker();
if (mWebView.hasWindowFocus()) setActive(false);
- removeAccessibilityApisFromJavaScript();
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector().removeAccessibilityApisIfNecessary();
+ } else {
+ // Ensure the injector is cleared if we're detaching from the window
+ // and accessibility is disabled.
+ mAccessibilityInjector = null;
+ }
+
updateHwAccelerated();
if (mWebView.isHardwareAccelerated()) {
@@ -7415,9 +7345,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
break;
case SELECTION_STRING_CHANGED:
- if (mAccessibilityInjector != null) {
- String selectionString = (String) msg.obj;
- mAccessibilityInjector.onSelectionStringChange(selectionString);
+ if (isAccessibilityEnabled()) {
+ getAccessibilityInjector()
+ .handleSelectionChangedIfNecessary((String) msg.obj);
}
break;
@@ -7486,6 +7416,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
int cursorPosition = start + text.length();
replaceTextfieldText(start, end, text,
cursorPosition, cursorPosition);
+ selectionDone();
break;
}
@@ -7512,7 +7443,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
case CLEAR_CARET_HANDLE:
- selectionDone();
+ if (mIsCaretSelection) {
+ selectionDone();
+ }
break;
case KEY_PRESS:
@@ -7600,6 +7533,11 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
invalidate();
}
}
+
+ @Override
+ public void clearPreviousHitTest() {
+ setHitTestResult(null);
+ }
}
private void setHitTestTypeFromUrl(String url) {
@@ -7973,7 +7911,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
mIsBatchingTextChanges = false;
}
- private void sendBatchableInputMessage(int what, int arg1, int arg2,
+ void sendBatchableInputMessage(int what, int arg1, int arg2,
Object obj) {
if (mWebViewCore == null) {
return;
@@ -8394,16 +8332,6 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc
}
/**
- * @return Whether accessibility script has been injected.
- */
- private boolean accessibilityScriptInjected() {
- // TODO: Maybe the injected script should announce its presence in
- // the page meta-tag so the nativePageShouldHandleShiftAndArrows
- // will check that as one of the conditions it looks for
- return mAccessibilityScriptInjected;
- }
-
- /**
* See {@link WebView#setBackgroundColor(int)}
*/
@Override
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 76cd1c9..af7914e 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -178,8 +178,10 @@ public final class WebViewCore {
// Start the singleton watchdog which will monitor the WebCore thread
// to verify it's still processing messages.
- WebCoreThreadWatchdog.start(context, sWebCoreHandler);
+ WebCoreThreadWatchdog.start(sWebCoreHandler);
}
+ // Make sure the Watchdog is aware of this new WebView.
+ WebCoreThreadWatchdog.registerWebView(w);
}
// Create an EventHub to handle messages before and after the thread is
// ready.
@@ -1979,6 +1981,7 @@ public final class WebViewCore {
mEventHub.sendMessageAtFrontOfQueue(
Message.obtain(null, EventHub.DESTROY));
mEventHub.blockMessages();
+ WebCoreThreadWatchdog.unregisterWebView(mWebViewClassic);
}
}
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
index 9eeb311..d118eac 100644
--- a/core/java/android/webkit/WebViewInputDispatcher.java
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -399,7 +399,6 @@ final class WebViewInputDispatcher {
unscheduleHideTapHighlightLocked();
unscheduleShowTapHighlightLocked();
mUiCallbacks.showTapHighlight(true);
- scheduleHideTapHighlightLocked();
}
private void scheduleShowTapHighlightLocked() {
@@ -466,13 +465,13 @@ final class WebViewInputDispatcher {
return;
}
mPostClickScheduled = false;
- showTapCandidateLocked();
MotionEvent event = mPostTouchStream.getLastEvent();
if (event == null || event.getAction() != MotionEvent.ACTION_UP) {
return;
}
+ showTapCandidateLocked();
MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_CLICK, 0,
mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
@@ -511,6 +510,7 @@ final class WebViewInputDispatcher {
}
private void enqueueHitTestLocked(MotionEvent event) {
+ mUiCallbacks.clearPreviousHitTest();
MotionEvent eventToEnqueue = MotionEvent.obtainNoHistory(event);
DispatchEvent d = obtainDispatchEventLocked(eventToEnqueue, EVENT_TYPE_HIT_TEST, 0,
mPostLastWebKitXOffset, mPostLastWebKitYOffset, mPostLastWebKitScale);
@@ -666,6 +666,10 @@ final class WebViewInputDispatcher {
if (event != null && recycleEvent) {
event.recycle();
}
+
+ if (eventType == EVENT_TYPE_CLICK) {
+ scheduleHideTapHighlightLocked();
+ }
}
}
}
@@ -802,6 +806,10 @@ final class WebViewInputDispatcher {
d.mEvent = null; // retain ownership of event, don't recycle it yet
}
recycleDispatchEventLocked(d);
+
+ if (eventType == EVENT_TYPE_CLICK) {
+ scheduleHideTapHighlightLocked();
+ }
}
// Handle the event.
@@ -822,21 +830,31 @@ final class WebViewInputDispatcher {
}
private void enqueueEventLocked(DispatchEvent d) {
- if (!shouldSkipWebKit(d.mEventType)) {
+ if (!shouldSkipWebKit(d)) {
enqueueWebKitEventLocked(d);
} else {
enqueueUiEventLocked(d);
}
}
- private boolean shouldSkipWebKit(int eventType) {
- switch (eventType) {
+ private boolean shouldSkipWebKit(DispatchEvent d) {
+ switch (d.mEventType) {
case EVENT_TYPE_CLICK:
case EVENT_TYPE_HOVER:
case EVENT_TYPE_SCROLL:
case EVENT_TYPE_HIT_TEST:
return false;
case EVENT_TYPE_TOUCH:
+ // TODO: This should be cleaned up. We now have WebViewInputDispatcher
+ // and WebViewClassic both checking for slop and doing their own
+ // thing - they should be consolidated. And by consolidated, I mean
+ // WebViewClassic's version should just be deleted.
+ // The reason this is done is because webpages seem to expect
+ // that they only get an ontouchmove if the slop has been exceeded.
+ if (mIsTapCandidate && d.mEvent != null
+ && d.mEvent.getActionMasked() == MotionEvent.ACTION_MOVE) {
+ return true;
+ }
return !mPostSendTouchEventsToWebKit
|| mPostDoNotSendTouchEventsToWebKitUntilNextGesture;
}
@@ -1040,6 +1058,12 @@ final class WebViewInputDispatcher {
* @param show True if it should show the highlight, false if it should hide it
*/
public void showTapHighlight(boolean show);
+
+ /**
+ * Called when we are sending a new EVENT_TYPE_HIT_TEST to WebKit, so
+ * previous hit tests should be cleared as they are obsolete.
+ */
+ public void clearPreviousHitTest();
}
/* Implemented by {@link WebViewCore} to perform operations on the web kit thread. */
diff --git a/core/java/android/webkit/WebViewProvider.java b/core/java/android/webkit/WebViewProvider.java
index 74a215c..867ee54 100644
--- a/core/java/android/webkit/WebViewProvider.java
+++ b/core/java/android/webkit/WebViewProvider.java
@@ -276,6 +276,8 @@ public interface WebViewProvider {
public void onInitializeAccessibilityEvent(AccessibilityEvent event);
+ public boolean performAccessibilityAction(int action, Bundle arguments);
+
public void setOverScrollMode(int mode);
public void setScrollBarStyle(int style);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index 9ae61bc..f2aaab7 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -16,6 +16,7 @@ extern jfieldID gOptions_widthFieldID;
extern jfieldID gOptions_heightFieldID;
extern jfieldID gOptions_mimeFieldID;
extern jfieldID gOptions_mCancelID;
+extern jfieldID gOptions_bitmapFieldID;
jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format);
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index dd8e84f..b218bcd 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -29,6 +29,7 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "Utils.h"
#include "JNIHelp.h"
+#include "SkTScopedPtr.h"
#include <android_runtime/AndroidRuntime.h>
#include "android_util_Binder.h"
@@ -180,7 +181,8 @@ static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz,
* reportSizeToVM not supported
*/
static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd,
- int start_x, int start_y, int width, int height, jobject options) {
+ int start_x, int start_y, int width, int height, jobject options) {
+ jobject tileBitmap = NULL;
SkImageDecoder *decoder = brd->getDecoder();
int sampleSize = 1;
SkBitmap::Config prefConfig = SkBitmap::kNo_Config;
@@ -199,12 +201,12 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b
doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
preferQualityOverSpeed = env->GetBooleanField(options,
gOptions_preferQualityOverSpeedFieldID);
+ // Get the bitmap for re-use if it exists.
+ tileBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
}
decoder->setDitherImage(doDither);
decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
- SkBitmap* bitmap = new SkBitmap;
- SkAutoTDelete<SkBitmap> adb(bitmap);
AutoDecoderCancel adc(options, decoder);
// To fix the race condition in case "requestCancelDecode"
@@ -219,6 +221,17 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b
region.fTop = start_y;
region.fRight = start_x + width;
region.fBottom = start_y + height;
+ SkBitmap* bitmap = NULL;
+ SkTScopedPtr<SkBitmap> adb;
+
+ if (tileBitmap != NULL) {
+ // Re-use bitmap.
+ bitmap = GraphicsJNI::getNativeBitmap(env, tileBitmap);
+ }
+ if (bitmap == NULL) {
+ bitmap = new SkBitmap;
+ adb.reset(bitmap);
+ }
if (!brd->decodeRegion(bitmap, region, prefConfig, sampleSize)) {
return nullObjectReturn("decoder->decodeRegion returned false");
@@ -235,12 +248,12 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b
getMimeTypeString(env, decoder->getFormat()));
}
- // detach bitmap from its autodeleter, since we want to own it now
- adb.detach();
+ if (tileBitmap != NULL) {
+ return tileBitmap;
+ }
- SkPixelRef* pr = bitmap->pixelRef();
- // promise we will never change our pixels (great for sharing and pictures)
- pr->setImmutable();
+ // detach bitmap from its autodeleter, since we want to own it now
+ adb.release();
JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator();
jbyteArray buff = allocator->getStorageObjAndReset();
diff --git a/core/res/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png b/core/res/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png
index 2fc475b..9c5147e 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_activated_holo_light.9.png b/core/res/res/drawable-hdpi/switch_thumb_activated_holo_light.9.png
index 5adecf1..9c5147e 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_activated_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_activated_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png b/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png
index 457fa84..a257e26 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_light.9.png b/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_light.9.png
index c3cfc29..a257e26 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_holo_dark.9.png b/core/res/res/drawable-hdpi/switch_thumb_holo_dark.9.png
index d0e1806..dd999d6 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_holo_light.9.png b/core/res/res/drawable-hdpi/switch_thumb_holo_light.9.png
index c30506d..dd999d6 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png b/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png
index 9106687..ea54380 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_light.9.png b/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_light.9.png
index 2bdda56..ea54380 100644
--- a/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_light.9.png
+++ b/core/res/res/drawable-hdpi/switch_thumb_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png b/core/res/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png
index 0787d16..3d7c236 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_activated_holo_light.9.png b/core/res/res/drawable-mdpi/switch_thumb_activated_holo_light.9.png
index 0157e68..3d7c236 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_activated_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_activated_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png b/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png
index 51b14d0..82f05d6 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_light.9.png b/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_light.9.png
index d68568a..82f05d6 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_holo_dark.9.png b/core/res/res/drawable-mdpi/switch_thumb_holo_dark.9.png
index 6bf153a..9bc7a68 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_holo_light.9.png b/core/res/res/drawable-mdpi/switch_thumb_holo_light.9.png
index 0d98983..9bc7a68 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png b/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png
index 3cee7b8..670dc2e 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_light.9.png b/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_light.9.png
index 43a7c4c..670dc2e 100644
--- a/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_light.9.png
+++ b/core/res/res/drawable-mdpi/switch_thumb_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png b/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png
index a0e6b20..ca48bd8 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_light.9.png b/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_light.9.png
index 88235fe..ca48bd8 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_activated_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png b/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png
index 04fb9a1..c3d80f0 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_light.9.png b/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_light.9.png
index 06a14f3..c3d80f0 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_disabled_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_holo_dark.9.png b/core/res/res/drawable-xhdpi/switch_thumb_holo_dark.9.png
index af7d631..df236df 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_holo_light.9.png b/core/res/res/drawable-xhdpi/switch_thumb_holo_light.9.png
index d6ab3ea..df236df 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png b/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png
index 5a8e807..4acb32b 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_dark.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_light.9.png b/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_light.9.png
index 392f3dc..4acb32b 100644
--- a/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_light.9.png
+++ b/core/res/res/drawable-xhdpi/switch_thumb_pressed_holo_light.9.png
Binary files differ
diff --git a/core/res/res/drawable/switch_track_holo_dark.xml b/core/res/res/drawable/switch_track_holo_dark.xml
index c9a940d..5f796c1 100644
--- a/core/res/res/drawable/switch_track_holo_dark.xml
+++ b/core/res/res/drawable/switch_track_holo_dark.xml
@@ -15,7 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:drawable="@drawable/switch_bg_disabled_holo_dark" />
<item android:state_focused="true" android:drawable="@drawable/switch_bg_focused_holo_dark" />
<item android:drawable="@drawable/switch_bg_holo_dark" />
</selector>
diff --git a/core/res/res/drawable/switch_track_holo_light.xml b/core/res/res/drawable/switch_track_holo_light.xml
index 98e53b5..39bee37 100644
--- a/core/res/res/drawable/switch_track_holo_light.xml
+++ b/core/res/res/drawable/switch_track_holo_light.xml
@@ -15,7 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:drawable="@drawable/switch_bg_disabled_holo_light" />
<item android:state_focused="true" android:drawable="@drawable/switch_bg_focused_holo_light" />
<item android:drawable="@drawable/switch_bg_holo_light" />
</selector>
diff --git a/core/tests/coretests/src/android/net/SSLTest.java b/core/tests/coretests/src/android/net/SSLTest.java
index c573498..27b699d 100644
--- a/core/tests/coretests/src/android/net/SSLTest.java
+++ b/core/tests/coretests/src/android/net/SSLTest.java
@@ -59,17 +59,20 @@ public class SSLTest extends TestCase {
new byte[] { 'h', 't', 't', 'p', '/', '1', '.', '1' })));
}
- public void testStringsToNpnBytesEmptyByteArray() {
+ public void testStringsToNpnBytesEmptyArray() {
try {
- SSLCertificateSocketFactory.toNpnProtocolsList(new byte[0]);
+ SSLCertificateSocketFactory.toNpnProtocolsList();
fail();
} catch (IllegalArgumentException expected) {
}
}
- public void testStringsToNpnBytesEmptyArray() {
- byte[] expected = {};
- assertTrue(Arrays.equals(expected, SSLCertificateSocketFactory.toNpnProtocolsList()));
+ public void testStringsToNpnBytesEmptyByteArray() {
+ try {
+ SSLCertificateSocketFactory.toNpnProtocolsList(new byte[0]);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
}
public void testStringsToNpnBytesOversizedInput() {
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 496e0c7..c1d3407 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -180,9 +180,9 @@ public final class BitmapRegionDecoder {
*/
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
checkRecycled("decodeRegion called on recycled region decoder");
- if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
- || rect.bottom > getHeight())
- throw new IllegalArgumentException("rectangle is not inside the image");
+ if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
+ || rect.top >= getHeight())
+ throw new IllegalArgumentException("rectangle is outside the image");
return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, options);
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index ef5da5b..16cfa92 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2000,6 +2000,37 @@ public class AudioManager {
}
/**
+ * @hide
+ * Used internally by telephony package to register an intent receiver for ACTION_MEDIA_BUTTON.
+ * @param eventReceiver the component that will receive the media button key events,
+ * no-op if eventReceiver is null
+ */
+ public void registerMediaButtonEventReceiverForCalls(ComponentName eventReceiver) {
+ if (eventReceiver == null) {
+ return;
+ }
+ IAudioService service = getService();
+ try {
+ // eventReceiver != null
+ service.registerMediaButtonEventReceiverForCalls(eventReceiver);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in registerMediaButtonEventReceiverForCalls", e);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void unregisterMediaButtonEventReceiverForCalls() {
+ IAudioService service = getService();
+ try {
+ service.unregisterMediaButtonEventReceiverForCalls();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in unregisterMediaButtonEventReceiverForCalls", e);
+ }
+ }
+
+ /**
* Unregister the receiver of MEDIA_BUTTON intents.
* @param eventReceiver identifier of a {@link android.content.BroadcastReceiver}
* that was registered with {@link #registerMediaButtonEventReceiver(ComponentName)}.
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 8da7d0f..aa29444 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -3625,12 +3625,14 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
return;
}
- // event filtering based on audio mode
+ // event filtering for telephony
synchronized(mRingingLock) {
- if (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL) ||
- (getMode() == AudioSystem.MODE_IN_COMMUNICATION) ||
- (getMode() == AudioSystem.MODE_RINGTONE) ) {
- return;
+ synchronized(mRCStack) {
+ if ((mMediaReceiverForCalls != null) &&
+ (mIsRinging || (getMode() == AudioSystem.MODE_IN_CALL))) {
+ dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
+ return;
+ }
}
}
// event filtering based on voice-based interactions
@@ -3642,6 +3644,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
/**
+ * Handles the dispatching of the media button events to the telephony package.
+ * Precondition: mMediaReceiverForCalls != null
+ * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
+ * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
+ * is dispatched.
+ */
+ private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
+ Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
+ keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+ keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
+ if (needWakeLock) {
+ mMediaEventWakeLock.acquire();
+ keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
+ }
+ mContext.sendOrderedBroadcast(keyIntent, null, mKeyEventDone,
+ mAudioHandler, Activity.RESULT_OK, null, null);
+ }
+
+ /**
* Handles the dispatching of the media button events to one of the registered listeners,
* or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
* @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
@@ -3678,38 +3699,15 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
/**
- * The minimum duration during which a user must press to trigger voice-based interactions
- */
- private final static int MEDIABUTTON_LONG_PRESS_DURATION_MS = 300;
- /**
- * The different states of the state machine to handle the launch of voice-based interactions,
- * stored in mVoiceButtonState.
- */
- private final static int VOICEBUTTON_STATE_IDLE = 0;
- private final static int VOICEBUTTON_STATE_DOWN = 1;
- private final static int VOICEBUTTON_STATE_DOWN_IGNORE_NEW = 2;
- /**
- * The different actions after state transitions on mVoiceButtonState.
+ * The different actions performed in response to a voice button key event.
*/
private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
private final Object mVoiceEventLock = new Object();
- private int mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
- private long mVoiceButtonDownTime = 0;
-
- /**
- * Log an error when an unexpected action is encountered in the state machine to filter
- * key events.
- * @param keyAction the unexpected action of the key event being filtered
- * @param stateName the string corresponding to the state in which the error occurred
- */
- private static void logErrorForKeyAction(int keyAction, String stateName) {
- Log.e(TAG, "unexpected action "
- + KeyEvent.actionToString(keyAction)
- + " in " + stateName + " state");
- }
+ private boolean mVoiceButtonDown;
+ private boolean mVoiceButtonHandled;
/**
* Filter key events that may be used for voice-based interactions
@@ -3719,67 +3717,32 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
* is dispatched.
*/
private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
+ if (DEBUG_RC) {
+ Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
+ }
+
int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
int keyAction = keyEvent.getAction();
synchronized (mVoiceEventLock) {
- // state machine on mVoiceButtonState
- switch (mVoiceButtonState) {
-
- case VOICEBUTTON_STATE_IDLE:
- if (keyAction == KeyEvent.ACTION_DOWN) {
- mVoiceButtonDownTime = keyEvent.getDownTime();
- // valid state transition
- mVoiceButtonState = VOICEBUTTON_STATE_DOWN;
- } else if (keyAction == KeyEvent.ACTION_UP) {
- // no state transition
- // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
- } else {
- logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_IDLE");
- }
- break;
-
- case VOICEBUTTON_STATE_DOWN:
- if ((keyEvent.getEventTime() - mVoiceButtonDownTime)
- >= MEDIABUTTON_LONG_PRESS_DURATION_MS) {
- // press was long enough, start voice-based interactions, regardless of
- // whether this was a DOWN or UP key event
- voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
- if (keyAction == KeyEvent.ACTION_UP) {
- // done tracking the key press, so transition back to idle state
- mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
- } else if (keyAction == KeyEvent.ACTION_DOWN) {
- // no need to observe the upcoming key events
- mVoiceButtonState = VOICEBUTTON_STATE_DOWN_IGNORE_NEW;
- } else {
- logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN");
- }
- } else {
- if (keyAction == KeyEvent.ACTION_UP) {
- // press wasn't long enough, simulate complete key press
- voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
- // not tracking the key press anymore, so transition back to idle state
- mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
- } else if (keyAction == KeyEvent.ACTION_DOWN) {
- // no state transition
- // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
- } else {
- logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN");
- }
- }
- break;
-
- case VOICEBUTTON_STATE_DOWN_IGNORE_NEW:
- if (keyAction == KeyEvent.ACTION_UP) {
- // done tracking the key press, so transition back to idle state
- mVoiceButtonState = VOICEBUTTON_STATE_IDLE;
- // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
- } else if (keyAction == KeyEvent.ACTION_DOWN) {
- // no state transition: we've already launched voice-based interactions
- // action is still VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS
- } else {
- logErrorForKeyAction(keyAction, "VOICEBUTTON_STATE_DOWN_IGNORE_NEW");
+ if (keyAction == KeyEvent.ACTION_DOWN) {
+ if (keyEvent.getRepeatCount() == 0) {
+ // initial down
+ mVoiceButtonDown = true;
+ mVoiceButtonHandled = false;
+ } else if (mVoiceButtonDown && !mVoiceButtonHandled
+ && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
+ // long-press, start voice-based interactions
+ mVoiceButtonHandled = true;
+ voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
+ }
+ } else if (keyAction == KeyEvent.ACTION_UP) {
+ if (mVoiceButtonDown) {
+ // voice button up
+ mVoiceButtonDown = false;
+ if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
+ voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
}
- break;
+ }
}
}//synchronized (mVoiceEventLock)
@@ -4028,6 +3991,12 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
private final Stack<RemoteControlStackEntry> mRCStack = new Stack<RemoteControlStackEntry>();
/**
+ * The component the telephony package can register so telephony calls have priority to
+ * handle media button events
+ */
+ private ComponentName mMediaReceiverForCalls = null;
+
+ /**
* Helper function:
* Display in the log the current entries in the remote control focus stack
*/
@@ -4381,6 +4350,35 @@ public class AudioService extends IAudioService.Stub implements OnFinished {
}
/**
+ * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
+ * precondition: c != null
+ */
+ public void registerMediaButtonEventReceiverForCalls(ComponentName c) {
+ if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Invalid permissions to register media button receiver for calls");
+ return;
+ }
+ synchronized(mRCStack) {
+ mMediaReceiverForCalls = c;
+ }
+ }
+
+ /**
+ * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
+ */
+ public void unregisterMediaButtonEventReceiverForCalls() {
+ if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
+ != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
+ return;
+ }
+ synchronized(mRCStack) {
+ mMediaReceiverForCalls = null;
+ }
+ }
+
+ /**
* see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
* Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
* without modifying the RC stack, but while still causing the display to refresh (will
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 48f091c..6753ad3 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -109,6 +109,9 @@ interface IAudioService {
oneway void registerMediaButtonIntent(in PendingIntent pi, in ComponentName c);
oneway void unregisterMediaButtonIntent(in PendingIntent pi, in ComponentName c);
+ oneway void registerMediaButtonEventReceiverForCalls(in ComponentName c);
+ oneway void unregisterMediaButtonEventReceiverForCalls();
+
oneway void registerRemoteControlClient(in PendingIntent mediaIntent,
in IRemoteControlClient rcClient, in String callingPackageName);
oneway void unregisterRemoteControlClient(in PendingIntent mediaIntent,
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index aa4cdbe..9ed9de0 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -737,7 +737,7 @@ public class MediaPlayer
* @see MediaPlayer#VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
*/
public void setVideoScalingMode(int mode) {
- if (isVideoScalingModeSupported(mode)) {
+ if (!isVideoScalingModeSupported(mode)) {
final String msg = "Scaling mode " + mode + " is not supported";
throw new IllegalArgumentException(msg);
}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 821a251b..987c0ac 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -1623,7 +1623,7 @@ public class MediaScanner
if (line.startsWith("File")) {
int equals = line.indexOf('=');
if (equals > 0) {
- cachePlaylistEntry(line, playListDirectory);
+ cachePlaylistEntry(line.substring(equals + 1), playListDirectory);
}
}
line = reader.readLine();
diff --git a/packages/InputDevices/AndroidManifest.xml b/packages/InputDevices/AndroidManifest.xml
index 6831a74..f0e4abc 100644
--- a/packages/InputDevices/AndroidManifest.xml
+++ b/packages/InputDevices/AndroidManifest.xml
@@ -8,7 +8,8 @@
android:label="@string/app_label"
android:process="system">
- <receiver android:name=".InputDeviceReceiver">
+ <receiver android:name=".InputDeviceReceiver"
+ android:label="@string/keyboard_layouts_label">
<intent-filter>
<action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" />
</intent-filter>
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 140c7d4..c13e606 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -3,6 +3,9 @@
<!-- Name of the application. [CHAR LIMIT=35] -->
<string name="app_label">Input Devices</string>
+ <!-- Keyboard layouts label, used to describe the set of all built-in layouts in the UI. [CHAR LIMIT=35] -->
+ <string name="keyboard_layouts_label">Android keyboard</string>
+
<!-- US English keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_english_us_label">English (US)</string>
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
index daf18c7..d9ec745 100644
--- a/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..117cf19
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
index 5292998..2cebe85 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..7c60bea
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..9e6323c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..09b35b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-hdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..2220c73
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..ea987f1
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-mdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png
new file mode 100644
index 0000000..21db6a2
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..5e8baa4
--- /dev/null
+++ b/packages/SystemUI/res/drawable-sw600dp-xhdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png
index 3e7fefd..90b8c84 100644
--- a/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/stat_sys_wifi_signal_null.png b/packages/SystemUI/res/drawable-xhdpi/stat_sys_wifi_signal_null.png
new file mode 100644
index 0000000..5881402
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/stat_sys_wifi_signal_null.png
Binary files differ
diff --git a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml b/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml
index b9af3a9..f93dd33 100644
--- a/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml
+++ b/packages/SystemUI/res/layout-sw600dp/super_status_bar.xml
@@ -26,15 +26,16 @@
android:fitsSystemWindows="true"
>
+ <include layout="@layout/status_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@*android:dimen/status_bar_height"
+ />
+
<include layout="@layout/status_bar_expanded"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal|top"
- />
-
- <include layout="@layout/status_bar"
- android:layout_width="match_parent"
- android:layout_height="@*android:dimen/status_bar_height"
+ android:visibility="invisible"
/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 6c31ff4..b85686f 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -26,14 +26,15 @@
android:fitsSystemWindows="true"
>
- <include layout="@layout/status_bar_expanded"
+ <include layout="@layout/status_bar"
android:layout_width="match_parent"
- android:layout_height="match_parent"
+ android:layout_height="@*android:dimen/status_bar_height"
/>
- <include layout="@layout/status_bar"
+ <include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
- android:layout_height="@*android:dimen/status_bar_height"
+ android:layout_height="match_parent"
+ android:visibility="invisible"
/>
</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 07d55f1..be59075 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -33,9 +33,6 @@
<!-- Height of search panel including navigation bar height -->
<dimen name="navbar_search_panel_height">300dip</dimen>
- <!-- Extra space above the clock in the panel; on this device, zero -->
- <dimen name="notification_panel_header_padding_top">0dp</dimen>
-
<!-- Size of application thumbnail -->
<dimen name="status_bar_recents_thumbnail_width">200dp</dimen>
<dimen name="status_bar_recents_thumbnail_height">177dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0d79a9b..b1611d1 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -132,10 +132,10 @@
<!-- Height of the notification panel header bar -->
<dimen name="notification_panel_header_height">48dp</dimen>
- <!-- Height of the notification panel header bar -->
- <dimen name="notification_panel_padding_top">@*android:dimen/status_bar_height</dimen>
+ <!-- Extra space above the panel -->
+ <dimen name="notification_panel_padding_top">4dp</dimen>
- <!-- Extra space above the clock in the panel; half of (notification_panel_header_height - 32) -->
+ <!-- Extra space above the clock in the panel -->
<dimen name="notification_panel_header_padding_top">0dp</dimen>
<!-- Layout parameters for the notification panel -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index af77a30..a23fe12 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -59,7 +59,7 @@
<style name="TextAppearance.StatusBar.Expanded.Date">
<item name="android:textSize">12dp</item>
<item name="android:textStyle">normal</item>
- <item name="android:textColor">#666666</item>
+ <item name="android:textColor">#cccccc</item>
<item name="android:textAllCaps">true</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index c60c806..96f83c6 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -281,9 +281,16 @@ public class ImageWallpaper extends WallpaperService {
return;
}
- if (mBackgroundWidth < 0 || mBackgroundHeight < 0) {
- // If we don't yet know the size of the wallpaper bitmap,
- // we need to get it now.
+ // If we don't yet know the size of the wallpaper bitmap,
+ // we need to get it now.
+ boolean updateWallpaper = mBackgroundWidth < 0 || mBackgroundHeight < 0 ;
+
+ // If we somehow got to this point after we have last flushed
+ // the wallpaper, well we really need it to draw again. So
+ // seems like we need to reload it. Ouch.
+ updateWallpaper = updateWallpaper || mBackground == null;
+
+ if (updateWallpaper) {
updateWallpaperLocked();
}
@@ -308,12 +315,6 @@ public class ImageWallpaper extends WallpaperService {
mLastXTranslation = xPixels;
mLastYTranslation = yPixels;
- if (mBackground == null) {
- // If we somehow got to this point after we have last flushed
- // the wallpaper, well we really need it to draw again. So
- // seems like we need to reload it. Ouch.
- updateWallpaperLocked();
- }
if (mIsHwAccelerated) {
if (!drawWallpaperWithOpenGL(sh, availw, availh, xPixels, yPixels)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index 2f02d23..1321ade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -21,9 +21,9 @@ import android.util.AttributeSet;
import android.util.Slog;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -31,12 +31,12 @@ import com.android.systemui.R;
// Intimately tied to the design of res/layout/signal_cluster_view.xml
public class SignalClusterView
- extends LinearLayout
+ extends LinearLayout
implements NetworkController.SignalCluster {
static final boolean DEBUG = false;
static final String TAG = "SignalClusterView";
-
+
NetworkController mNC;
private boolean mWifiVisible = false;
@@ -132,6 +132,17 @@ public class SignalClusterView
apply();
}
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ // Standard group layout onPopulateAccessibilityEvent() implementations
+ // ignore content description, so populate manually
+ if (mWifiVisible && mWifiGroup.getContentDescription() != null)
+ event.getText().add(mWifiGroup.getContentDescription());
+ if (mMobileVisible && mMobileGroup.getContentDescription() != null)
+ event.getText().add(mMobileGroup.getContentDescription());
+ return super.dispatchPopulateAccessibilityEvent(event);
+ }
+
// Run after each indicator change.
private void apply() {
if (mWifiGroup == null) return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 4e6857e..69d2e73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -301,6 +301,9 @@ public class PhoneStatusBar extends BaseStatusBar {
return true;
}
});
+ mNotificationPanel.setSystemUiVisibility(
+ View.STATUS_BAR_DISABLE_NOTIFICATION_TICKER
+ | View.STATUS_BAR_DISABLE_SYSTEM_INFO);
if (!ActivityManager.isHighEndGfx(mDisplay)) {
mStatusBarWindow.setBackground(null);
@@ -336,7 +339,6 @@ public class PhoneStatusBar extends BaseStatusBar {
mPixelFormat = PixelFormat.OPAQUE;
mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons);
mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons);
- mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon);
mNotificationIcons.setOverflowIndicator(mMoreIcon);
mIcons = (LinearLayout)mStatusBarView.findViewById(R.id.icons);
mTickerView = mStatusBarView.findViewById(R.id.ticker);
@@ -884,6 +886,15 @@ public class PhoneStatusBar extends BaseStatusBar {
flagdbg.append(((diff & StatusBarManager.DISABLE_CLOCK) != 0) ? "* " : " ");
flagdbg.append(">");
Slog.d(TAG, flagdbg.toString());
+
+ if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+ mIcons.animate().cancel();
+ if ((state & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) {
+ mIcons.animate().alpha(0f).setStartDelay(100).setDuration(200).start();
+ } else {
+ mIcons.animate().alpha(1f).setStartDelay(0).setDuration(300).start();
+ }
+ }
if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) {
boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0;
@@ -985,6 +996,7 @@ public class PhoneStatusBar extends BaseStatusBar {
}
mExpandedVisible = true;
+ mNotificationPanel.setVisibility(View.VISIBLE);
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
@@ -1078,7 +1090,7 @@ public class PhoneStatusBar extends BaseStatusBar {
}
mExpandedVisible = false;
visibilityChanged(false);
- //mNotificationPanel.setVisibility(View.GONE);
+ mNotificationPanel.setVisibility(View.INVISIBLE);
// Shrink the window to the size of the status bar only
WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index a05fcc1..c65f581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -282,7 +282,8 @@ public class NetworkController extends BroadcastReceiver {
public void refreshSignalCluster(SignalCluster cluster) {
cluster.setWifiIndicators(
- mWifiConnected, // only show wifi in the cluster if connected
+ // only show wifi in the cluster if connected or if wifi-only
+ mWifiEnabled && (mWifiConnected || !mHasMobileDataFeature),
mWifiIconId,
mWifiActivityIconId,
mContentDescriptionWifi);
@@ -786,7 +787,7 @@ public class NetworkController extends BroadcastReceiver {
if (mDataAndWifiStacked) {
mWifiIconId = 0;
} else {
- mWifiIconId = mWifiEnabled ? WifiIcons.WIFI_SIGNAL_STRENGTH[0][0] : 0;
+ mWifiIconId = mWifiEnabled ? R.drawable.stat_sys_wifi_signal_null : 0;
}
mContentDescriptionWifi = mContext.getString(R.string.accessibility_no_wifi);
}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
index c46b94a..737ea47 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
@@ -300,7 +300,18 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* onServiceConnected() callback is received.
*/
void handleServiceConnected() {
- if (DEBUG) Log.d(TAG, "handleServiceConnected()");
+ Log.d(TAG, "handleServiceConnected()");
+
+ // It is possible that an unbind has occurred in the time between the bind and when this
+ // function is reached. If an unbind has already occurred, proceeding on to call startUi()
+ // can result in a fatal error. Note that the onServiceConnected() callback is
+ // asynchronous, so this possibility would still exist if we executed this directly in
+ // onServiceConnected() rather than using a handler.
+ if (!mBoundToService) {
+ Log.d(TAG, "Dropping startUi() in handleServiceConnected() because no longer bound");
+ return;
+ }
+
try {
mService.registerCallback(mFaceUnlockCallback);
} catch (RemoteException e) {
@@ -452,25 +463,12 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
* Tells the Face Unlock service to start displaying its UI and start processing.
*/
private void startUi(IBinder windowToken, int x, int y, int w, int h) {
- Log.d(TAG, "startUi()");
+ if (DEBUG) Log.d(TAG, "startUi()");
synchronized (mServiceRunningLock) {
if (!mServiceRunning) {
- if (DEBUG) Log.d(TAG, "Starting Face Unlock");
+ Log.d(TAG, "Starting Face Unlock");
try {
- // TODO: these checks and logs are for tracking down bug 6409767 and can be
- // removed when that bug is fixed.
- if (mService == null) {
- Log.d(TAG, "mService is null");
- }
- if (windowToken == null) {
- Log.d(TAG, "windowToken is null");
- }
- if (mLockPatternUtils == null) {
- Log.d(TAG, "mLockPatternUtils is null");
- }
- Log.d(TAG, "x,y,w,h,live: " + x + "," + y + "," + w + "," + h + ", no");
mService.startUi(windowToken, x, y, w, h, false);
- Log.d(TAG, "mService.startUi() called");
} catch (RemoteException e) {
Log.e(TAG, "Caught exception starting Face Unlock: " + e.toString());
return;
@@ -492,7 +490,7 @@ public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
// screen is turned off. That's why we check.
synchronized (mServiceRunningLock) {
if (mServiceRunning) {
- if (DEBUG) Log.d(TAG, "Stopping Face Unlock");
+ Log.d(TAG, "Stopping Face Unlock");
try {
mService.stopUi();
} catch (RemoteException e) {
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
index 7f432bf..504bb63 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewManager.java
@@ -117,7 +117,6 @@ public class KeyguardViewManager implements KeyguardWindowController {
final int stretch = ViewGroup.LayoutParams.MATCH_PARENT;
int flags = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN
| WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER
- | WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING
| WindowManager.LayoutParams.FLAG_SLIPPERY
/*| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR*/ ;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 8ab148e..cee01ac 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -158,7 +158,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final boolean DEBUG = false;
static final boolean localLOGV = false;
static final boolean DEBUG_LAYOUT = false;
- static final boolean DEBUG_FALLBACK = false;
+ static final boolean DEBUG_INPUT = false;
static final boolean SHOW_STARTING_ANIMATIONS = true;
static final boolean SHOW_PROCESSES_ON_ALT_MENU = false;
@@ -410,6 +410,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
// For applications requesting stable content insets, these are them.
int mStableLeft, mStableTop, mStableRight, mStableBottom;
+ // For applications requesting stable content insets but have also set the
+ // fullscreen window flag, these are the stable dimensions without the status bar.
+ int mStableFullscreenLeft, mStableFullscreenTop;
+ int mStableFullscreenRight, mStableFullscreenBottom;
// During layout, the current screen borders with all outer decoration
// (status bar, input method dock) accounted for.
int mCurLeft, mCurTop, mCurRight, mCurBottom;
@@ -500,6 +504,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
ShortcutManager mShortcutManager;
PowerManager.WakeLock mBroadcastWakeLock;
+ boolean mHavePendingMediaKeyRepeatWithWakeLock;
// Fallback actions by key code.
private final SparseArray<KeyCharacterMap.FallbackAction> mFallbackActions =
@@ -507,6 +512,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private static final int MSG_ENABLE_POINTER_LOCATION = 1;
private static final int MSG_DISABLE_POINTER_LOCATION = 2;
+ private static final int MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK = 3;
+ private static final int MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK = 4;
private class PolicyHandler extends Handler {
@Override
@@ -518,6 +525,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case MSG_DISABLE_POINTER_LOCATION:
disablePointerLocation();
break;
+ case MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK:
+ dispatchMediaKeyWithWakeLock((KeyEvent)msg.obj);
+ break;
+ case MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK:
+ dispatchMediaKeyRepeatWithWakeLock((KeyEvent)msg.obj);
+ break;
}
}
}
@@ -1688,7 +1701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
final boolean canceled = event.isCanceled();
- if (false) {
+ if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTi keyCode=" + keyCode + " down=" + down + " repeatCount="
+ repeatCount + " keyguardOn=" + keyguardOn + " mHomePressed=" + mHomePressed
+ " canceled=" + canceled);
@@ -1938,7 +1951,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
// Note: This method is only called if the initial down was unhandled.
- if (DEBUG_FALLBACK) {
+ if (DEBUG_INPUT) {
Slog.d(TAG, "Unhandled key: win=" + win + ", action=" + event.getAction()
+ ", flags=" + event.getFlags()
+ ", keyCode=" + event.getKeyCode()
@@ -1965,7 +1978,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
if (fallbackAction != null) {
- if (DEBUG_FALLBACK) {
+ if (DEBUG_INPUT) {
Slog.d(TAG, "Fallback: keyCode=" + fallbackAction.keyCode
+ " metaState=" + Integer.toHexString(fallbackAction.metaState));
}
@@ -1992,7 +2005,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- if (DEBUG_FALLBACK) {
+ if (DEBUG_INPUT) {
if (fallbackEvent == null) {
Slog.d(TAG, "No fallback.");
} else {
@@ -2143,22 +2156,31 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public void getContentInsetHintLw(WindowManager.LayoutParams attrs, Rect contentInset) {
final int fl = attrs.flags;
+ final int systemUiVisibility = (attrs.systemUiVisibility|attrs.subtreeSystemUiVisibility);
- if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR))
+ if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
int availRight, availBottom;
if (mCanHideNavigationBar &&
- (attrs.systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+ (systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
availRight = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
availBottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
} else {
availRight = mRestrictedScreenLeft + mRestrictedScreenWidth;
availBottom = mRestrictedScreenTop + mRestrictedScreenHeight;
}
- if ((attrs.systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- contentInset.set(mStableLeft, mStableTop,
- availRight - mStableRight, availBottom - mStableBottom);
- } else if ((attrs.systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
+ if ((systemUiVisibility & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ if ((fl & FLAG_FULLSCREEN) != 0) {
+ contentInset.set(mStableFullscreenLeft, mStableFullscreenTop,
+ availRight - mStableFullscreenRight,
+ availBottom - mStableFullscreenBottom);
+ } else {
+ contentInset.set(mStableLeft, mStableTop,
+ availRight - mStableRight, availBottom - mStableBottom);
+ }
+ } else if ((fl & FLAG_FULLSCREEN) != 0) {
+ contentInset.setEmpty();
+ } else if ((systemUiVisibility & (View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)) == 0) {
contentInset.set(mCurLeft, mCurTop,
availRight - mCurRight, availBottom - mCurBottom);
@@ -2179,10 +2201,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mRestrictedScreenLeft = mRestrictedScreenTop = 0;
mRestrictedScreenWidth = displayWidth;
mRestrictedScreenHeight = displayHeight;
- mDockLeft = mContentLeft = mStableLeft = mSystemLeft = mCurLeft = 0;
- mDockTop = mContentTop = mStableTop = mSystemTop = mCurTop = 0;
- mDockRight = mContentRight = mStableRight = mSystemRight = mCurRight = displayWidth;
- mDockBottom = mContentBottom = mStableBottom = mSystemBottom = mCurBottom = displayHeight;
+ mDockLeft = mContentLeft = mStableLeft = mStableFullscreenLeft
+ = mSystemLeft = mCurLeft = 0;
+ mDockTop = mContentTop = mStableTop = mStableFullscreenTop
+ = mSystemTop = mCurTop = 0;
+ mDockRight = mContentRight = mStableRight = mStableFullscreenRight
+ = mSystemRight = mCurRight = displayWidth;
+ mDockBottom = mContentBottom = mStableBottom = mStableFullscreenBottom
+ = mSystemBottom = mCurBottom = displayHeight;
mDockLayer = 0x10000000;
mStatusBarLayer = -1;
@@ -2235,7 +2261,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
mTmpNavigationFrame.set(0, top, displayWidth, displayHeight);
- mStableBottom = mTmpNavigationFrame.top;
+ mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
if (navVisible) {
mNavigationBar.showLw(true);
mDockBottom = mTmpNavigationFrame.top;
@@ -2259,7 +2285,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
mTmpNavigationFrame.set(left, 0, displayWidth, displayHeight);
- mStableRight = mTmpNavigationFrame.left;
+ mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
if (navVisible) {
mNavigationBar.showLw(true);
mDockRight = mTmpNavigationFrame.left;
@@ -2397,7 +2423,25 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
? attached.getFrameLw() : df);
}
-
+
+ private void applyStableConstraints(int sysui, int fl, Rect r) {
+ if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ // If app is requesting a stable layout, don't let the
+ // content insets go below the stable values.
+ if ((fl & FLAG_FULLSCREEN) != 0) {
+ if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
+ if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
+ if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
+ if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
+ } else {
+ if (r.left < mStableLeft) r.left = mStableLeft;
+ if (r.top < mStableTop) r.top = mStableTop;
+ if (r.right > mStableRight) r.right = mStableRight;
+ if (r.bottom > mStableBottom) r.bottom = mStableBottom;
+ }
+ }
+ }
+
/** {@inheritDoc} */
public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached) {
@@ -2504,14 +2548,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
cf.right = mContentRight;
cf.bottom = mContentBottom;
}
- if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- // If app is requesting a stable layout, don't let the
- // content insets go below the stable values.
- if (cf.left < mStableLeft) cf.left = mStableLeft;
- if (cf.top < mStableTop) cf.top = mStableTop;
- if (cf.right > mStableRight) cf.right = mStableRight;
- if (cf.bottom > mStableBottom) cf.bottom = mStableBottom;
- }
+ applyStableConstraints(sysUiFl, fl, cf);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.left = mCurLeft;
vf.top = mCurTop;
@@ -2593,14 +2630,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pf.bottom = df.bottom = cf.bottom
= mRestrictedScreenTop+mRestrictedScreenHeight;
}
- if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- // If app is requesting a stable layout, don't let the
- // content insets go below the stable values.
- if (cf.left < mStableLeft) cf.left = mStableLeft;
- if (cf.top < mStableTop) cf.top = mStableTop;
- if (cf.right > mStableRight) cf.right = mStableRight;
- if (cf.bottom > mStableBottom) cf.bottom = mStableBottom;
- }
+ applyStableConstraints(sysUiFl, fl, cf);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.left = mCurLeft;
vf.top = mCurTop;
@@ -3068,7 +3098,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return 0;
}
- if (false) {
+ if (DEBUG_INPUT) {
Log.d(TAG, "interceptKeyTq keycode=" + keyCode
+ " screenIsOn=" + isScreenOn + " keyguardActive=" + keyguardActive);
}
@@ -3290,8 +3320,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// Only do this if we would otherwise not pass it to the user. In that
// case, the PhoneWindow class will do the same thing, except it will
// only do it if the showing app doesn't process the key on its own.
+ // Note that we need to make a copy of the key event here because the
+ // original key event will be recycled when we return.
mBroadcastWakeLock.acquire();
- mHandler.post(new PassHeadsetKey(new KeyEvent(event)));
+ Message msg = mHandler.obtainMessage(MSG_DISPATCH_MEDIA_KEY_WITH_WAKE_LOCK,
+ new KeyEvent(event));
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
}
break;
}
@@ -3340,24 +3375,58 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return result;
}
- class PassHeadsetKey implements Runnable {
- KeyEvent mKeyEvent;
+ void dispatchMediaKeyWithWakeLock(KeyEvent event) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyWithWakeLock: " + event);
+ }
+
+ if (mHavePendingMediaKeyRepeatWithWakeLock) {
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyWithWakeLock: canceled repeat");
+ }
- PassHeadsetKey(KeyEvent keyEvent) {
- mKeyEvent = keyEvent;
+ mHandler.removeMessages(MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK);
+ mHavePendingMediaKeyRepeatWithWakeLock = false;
+ mBroadcastWakeLock.release(); // pending repeat was holding onto the wake lock
}
- public void run() {
- if (ActivityManagerNative.isSystemReady()) {
- IAudioService audioService = getAudioService();
- if (audioService != null) {
- try {
- audioService.dispatchMediaKeyEventUnderWakelock(mKeyEvent);
- } catch (RemoteException e) {
- Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
- }
+ dispatchMediaKeyWithWakeLockToAudioService(event);
+
+ if (event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ mHavePendingMediaKeyRepeatWithWakeLock = true;
+
+ Message msg = mHandler.obtainMessage(
+ MSG_DISPATCH_MEDIA_KEY_REPEAT_WITH_WAKE_LOCK, event);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, ViewConfiguration.getKeyRepeatTimeout());
+ } else {
+ mBroadcastWakeLock.release();
+ }
+ }
+
+ void dispatchMediaKeyRepeatWithWakeLock(KeyEvent event) {
+ mHavePendingMediaKeyRepeatWithWakeLock = false;
+
+ KeyEvent repeatEvent = KeyEvent.changeTimeRepeat(event,
+ SystemClock.uptimeMillis(), 1, event.getFlags() | KeyEvent.FLAG_LONG_PRESS);
+ if (DEBUG_INPUT) {
+ Slog.d(TAG, "dispatchMediaKeyRepeatWithWakeLock: " + repeatEvent);
+ }
+
+ dispatchMediaKeyWithWakeLockToAudioService(repeatEvent);
+ mBroadcastWakeLock.release();
+ }
+
+ void dispatchMediaKeyWithWakeLockToAudioService(KeyEvent event) {
+ if (ActivityManagerNative.isSystemReady()) {
+ IAudioService audioService = getAudioService();
+ if (audioService != null) {
+ try {
+ audioService.dispatchMediaKeyEventUnderWakelock(event);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dispatchMediaKeyEvent threw exception " + e);
}
- mBroadcastWakeLock.release();
}
}
}
@@ -4248,6 +4317,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print(","); pw.print(mRestrictedScreenTop);
pw.print(") "); pw.print(mRestrictedScreenWidth);
pw.print("x"); pw.println(mRestrictedScreenHeight);
+ pw.print(prefix); pw.print("mStableFullscreen=("); pw.print(mStableFullscreenLeft);
+ pw.print(","); pw.print(mStableFullscreenTop);
+ pw.print(")-("); pw.print(mStableFullscreenRight);
+ pw.print(","); pw.print(mStableFullscreenBottom); pw.println(")");
pw.print(prefix); pw.print("mStable=("); pw.print(mStableLeft);
pw.print(","); pw.print(mStableTop);
pw.print(")-("); pw.print(mStableRight);
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index a3768c6..2167c49 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -48,6 +48,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -252,6 +253,34 @@ class BackupManagerService extends IBackupManager.Stub {
IBackupTransport mLocalTransport, mGoogleTransport;
ActiveRestoreSession mActiveRestoreSession;
+ // Watch the device provisioning operation during setup
+ ContentObserver mProvisionedObserver;
+
+ class ProvisionedObserver extends ContentObserver {
+ public ProvisionedObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void onChange(boolean selfChange) {
+ final boolean wasProvisioned = mProvisioned;
+ final boolean isProvisioned = deviceIsProvisioned();
+ // latch: never unprovision
+ mProvisioned = wasProvisioned || isProvisioned;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "Provisioning change: was=" + wasProvisioned
+ + " is=" + isProvisioned + " now=" + mProvisioned);
+ }
+
+ synchronized (mQueueLock) {
+ if (mProvisioned && !wasProvisioned && mEnabled) {
+ // we're now good to go, so start the backup alarms
+ if (MORE_DEBUG) Slog.d(TAG, "Now provisioned, so starting backups");
+ startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
+ }
+ }
+ }
+ }
+
class RestoreGetSetsParams {
public IBackupTransport transport;
public ActiveRestoreSession session;
@@ -695,12 +724,19 @@ class BackupManagerService extends IBackupManager.Stub {
mBackupHandler = new BackupHandler(mHandlerThread.getLooper());
// Set up our bookkeeping
- boolean areEnabled = Settings.Secure.getInt(context.getContentResolver(),
+ final ContentResolver resolver = context.getContentResolver();
+ boolean areEnabled = Settings.Secure.getInt(resolver,
Settings.Secure.BACKUP_ENABLED, 0) != 0;
- mProvisioned = Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.BACKUP_PROVISIONED, 0) != 0;
- mAutoRestore = Settings.Secure.getInt(context.getContentResolver(),
+ mProvisioned = Settings.Secure.getInt(resolver,
+ Settings.Secure.DEVICE_PROVISIONED, 0) != 0;
+ mAutoRestore = Settings.Secure.getInt(resolver,
Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0;
+
+ mProvisionedObserver = new ProvisionedObserver(mBackupHandler);
+ resolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DEVICE_PROVISIONED),
+ false, mProvisionedObserver);
+
// If Encrypted file systems is enabled or disabled, this call will return the
// correct directory.
mBaseStateDir = new File(Environment.getSecureDataDirectory(), "backup");
@@ -5172,24 +5208,9 @@ class BackupManagerService extends IBackupManager.Stub {
public void setBackupProvisioned(boolean available) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"setBackupProvisioned");
-
- boolean wasProvisioned = mProvisioned;
- synchronized (this) {
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.BACKUP_PROVISIONED, available ? 1 : 0);
- mProvisioned = available;
- }
-
- synchronized (mQueueLock) {
- if (available && !wasProvisioned && mEnabled) {
- // we're now good to go, so start the backup alarms
- startBackupAlarmsLocked(FIRST_BACKUP_INTERVAL);
- } else if (!available) {
- // No longer enabled, so stop running backups
- Slog.w(TAG, "Backup service no longer provisioned");
- mAlarmManager.cancel(mRunBackupIntent);
- }
- }
+ /*
+ * This is now a no-op; provisioning is simply the device's own setup state.
+ */
}
private void startBackupAlarmsLocked(long delayBeforeFirstBackup) {
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index d6606f6..13ab586 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -79,6 +79,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.Set;
import javax.crypto.SecretKey;
@@ -178,7 +180,8 @@ class MountService extends IMountService.Stub
final private ArrayList<MountServiceBinderListener> mListeners =
new ArrayList<MountServiceBinderListener>();
private boolean mBooted = false;
- private boolean mReady = false;
+ private CountDownLatch mConnectedSignal = new CountDownLatch(1);
+ private CountDownLatch mAsecsScanned = new CountDownLatch(1);
private boolean mSendUmsConnectedOnBoot = false;
// true if we should fake MEDIA_MOUNTED state for external storage
private boolean mEmulateExternalStorage = false;
@@ -446,15 +449,30 @@ class MountService extends IMountService.Stub
final private HandlerThread mHandlerThread;
final private Handler mHandler;
+ void waitForAsecScan() {
+ waitForLatch(mAsecsScanned);
+ }
+
private void waitForReady() {
- while (mReady == false) {
- for (int retries = 5; retries > 0; retries--) {
- if (mReady) {
+ waitForLatch(mConnectedSignal);
+ }
+
+ private void waitForLatch(CountDownLatch latch) {
+ if (latch == null) {
+ return;
+ }
+
+ for (;;) {
+ try {
+ if (latch.await(5000, TimeUnit.MILLISECONDS)) {
return;
+ } else {
+ Slog.w(TAG, "Thread " + Thread.currentThread().getName()
+ + " still waiting for MountService ready...");
}
- SystemClock.sleep(1000);
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
}
- Slog.w(TAG, "Waiting too long for mReady!");
}
}
@@ -627,7 +645,7 @@ class MountService extends IMountService.Stub
* Since we'll be calling back into the NativeDaemonConnector,
* we need to do our work in a new thread.
*/
- new Thread() {
+ new Thread("MountService#onDaemonConnected") {
@Override
public void run() {
/**
@@ -668,14 +686,19 @@ class MountService extends IMountService.Stub
updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
}
- // Let package manager load internal ASECs.
- mPms.updateExternalMediaStatus(true, false);
-
/*
* Now that we've done our initialization, release
* the hounds!
*/
- mReady = true;
+ mConnectedSignal.countDown();
+ mConnectedSignal = null;
+
+ // Let package manager load internal ASECs.
+ mPms.scanAvailableAsecs();
+
+ // Notify people waiting for ASECs to be scanned that it's done.
+ mAsecsScanned.countDown();
+ mAsecsScanned = null;
}
}.start();
}
@@ -1159,22 +1182,12 @@ class MountService extends IMountService.Stub
mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
/*
- * Vold does not run in the simulator, so pretend the connector thread
- * ran and did its thing.
- */
- if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
- mReady = true;
- mUmsEnabling = true;
- return;
- }
-
- /*
* Create the connection to vold with a maximum queue of twice the
* amount of containers we'd ever expect to have. This keeps an
* "asec list" from blocking a thread repeatedly.
*/
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
- mReady = false;
+
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bb10358..eaecd4c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -320,6 +320,21 @@ class ServerThread extends Thread {
}
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+ MountService mountService = null;
+ if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+ try {
+ /*
+ * NotificationManagerService is dependant on MountService,
+ * (for media / usb notifications) so we must start MountService first.
+ */
+ Slog.i(TAG, "Mount Service");
+ mountService = new MountService(context);
+ ServiceManager.addService("mount", mountService);
+ } catch (Throwable e) {
+ reportWtf("starting Mount Service", e);
+ }
+ }
+
try {
Slog.i(TAG, "LockSettingsService");
lockSettings = new LockSettingsService(context);
@@ -441,17 +456,13 @@ class ServerThread extends Thread {
reportWtf("starting UpdateLockService", e);
}
- if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
- try {
- /*
- * NotificationManagerService is dependant on MountService,
- * (for media / usb notifications) so we must start MountService first.
- */
- Slog.i(TAG, "Mount Service");
- ServiceManager.addService("mount", new MountService(context));
- } catch (Throwable e) {
- reportWtf("starting Mount Service", e);
- }
+ /*
+ * MountService has a few dependencies: Notification Manager and
+ * AppWidget Provider. Make sure MountService is completely started
+ * first before continuing.
+ */
+ if (mountService != null) {
+ mountService.waitForAsecScan();
}
try {
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index b1558c7..1f03d17 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -376,11 +376,7 @@ public class WifiService extends IWifiManager.Stub {
@Override
public void onReceive(Context context, Intent intent) {
mAirplaneModeOn.set(isAirplaneModeOn());
- /* On airplane mode disable, restore wifi state if necessary */
- if (!mAirplaneModeOn.get() && (testAndClearWifiSavedState() ||
- mPersistWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE)) {
- persistWifiState(true);
- }
+ handleAirplaneModeToggled(mAirplaneModeOn.get());
updateWifiState();
}
},
@@ -447,7 +443,10 @@ public class WifiService extends IWifiManager.Stub {
boolean wifiEnabled = shouldWifiBeEnabled() || testAndClearWifiSavedState();
Slog.i(TAG, "WifiService starting up with Wi-Fi " +
(wifiEnabled ? "enabled" : "disabled"));
- setWifiEnabled(wifiEnabled);
+
+ // If we are already disabled (could be due to airplane mode), avoid changing persist
+ // state here
+ if (wifiEnabled) setWifiEnabled(wifiEnabled);
mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
makeWifiWatchdogStateMachine(mContext);
@@ -485,26 +484,43 @@ public class WifiService extends IWifiManager.Stub {
}
}
- private void persistWifiState(boolean enabled) {
- final ContentResolver cr = mContext.getContentResolver();
- boolean airplane = mAirplaneModeOn.get() && isAirplaneToggleable();
- if (enabled) {
- if (airplane) {
- mPersistWifiState.set(WIFI_ENABLED_AIRPLANE_OVERRIDE);
+ private void handleWifiToggled(boolean wifiEnabled) {
+ boolean airplaneEnabled = mAirplaneModeOn.get() && isAirplaneToggleable();
+ if (wifiEnabled) {
+ if (airplaneEnabled) {
+ persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
} else {
- mPersistWifiState.set(WIFI_ENABLED);
+ persistWifiState(WIFI_ENABLED);
}
} else {
- if (airplane) {
- mPersistWifiState.set(WIFI_DISABLED_AIRPLANE_ON);
- } else {
- mPersistWifiState.set(WIFI_DISABLED);
- }
+ // When wifi state is disabled, we do not care
+ // if airplane mode is on or not. The scenario of
+ // wifi being disabled due to airplane mode being turned on
+ // is handled handleAirplaneModeToggled()
+ persistWifiState(WIFI_DISABLED);
}
+ }
- Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, mPersistWifiState.get());
+ private void handleAirplaneModeToggled(boolean airplaneEnabled) {
+ if (airplaneEnabled) {
+ // Wifi disabled due to airplane on
+ if (mWifiEnabled) {
+ persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
+ }
+ } else {
+ /* On airplane mode disable, restore wifi state if necessary */
+ if (testAndClearWifiSavedState() ||
+ mPersistWifiState.get() == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
+ persistWifiState(WIFI_ENABLED);
+ }
+ }
}
+ private void persistWifiState(int state) {
+ final ContentResolver cr = mContext.getContentResolver();
+ mPersistWifiState.set(state);
+ Settings.Secure.putInt(cr, Settings.Secure.WIFI_ON, state);
+ }
/**
* see {@link android.net.wifi.WifiManager#pingSupplicant()}
@@ -578,12 +594,9 @@ public class WifiService extends IWifiManager.Stub {
* only CHANGE_WIFI_STATE is enforced
*/
- /* Avoids overriding of airplane state when wifi is already in the expected state */
- if (enable != mWifiEnabled) {
- long ident = Binder.clearCallingIdentity();
- persistWifiState(enable);
- Binder.restoreCallingIdentity(ident);
- }
+ long ident = Binder.clearCallingIdentity();
+ handleWifiToggled(enable);
+ Binder.restoreCallingIdentity(ident);
if (enable) {
if (!mIsReceiverRegistered) {
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 9e94b52..4f1f76f 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -37,7 +37,6 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
@@ -597,8 +596,8 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
@Override
public void visitKeyboardLayout(Resources resources,
- String descriptor, String label, int keyboardLayoutResId) {
- list.add(new KeyboardLayout(descriptor, label));
+ String descriptor, String label, String collection, int keyboardLayoutResId) {
+ list.add(new KeyboardLayout(descriptor, label, collection));
}
});
return list.toArray(new KeyboardLayout[list.size()]);
@@ -614,8 +613,8 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
@Override
public void visitKeyboardLayout(Resources resources,
- String descriptor, String label, int keyboardLayoutResId) {
- result[0] = new KeyboardLayout(descriptor, label);
+ String descriptor, String label, String collection, int keyboardLayoutResId) {
+ result[0] = new KeyboardLayout(descriptor, label, collection);
}
});
if (result[0] == null) {
@@ -663,6 +662,9 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
return;
}
+ CharSequence receiverLabel = receiver.loadLabel(pm);
+ String collection = receiverLabel != null ? receiverLabel.toString() : "";
+
try {
Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
XmlResourceParser parser = resources.getXml(configResId);
@@ -696,7 +698,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
receiver.packageName, receiver.name, name);
if (keyboardName == null || name.equals(keyboardName)) {
visitor.visitKeyboardLayout(resources, descriptor,
- label, keyboardLayoutResId);
+ label, collection, keyboardLayoutResId);
}
}
} finally {
@@ -1139,7 +1141,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
@Override
public void visitKeyboardLayout(Resources resources,
- String descriptor, String label, int keyboardLayoutResId) {
+ String descriptor, String label, String collection, int keyboardLayoutResId) {
try {
result[0] = descriptor;
result[1] = Streams.readFully(new InputStreamReader(
@@ -1262,7 +1264,7 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
private interface KeyboardLayoutVisitor {
void visitKeyboardLayout(Resources resources,
- String descriptor, String label, int keyboardLayoutResId);
+ String descriptor, String label, String collection, int keyboardLayoutResId);
}
private final class InputDevicesChangedListenerRecord implements DeathRecipient {
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 3936c18..77c3e78 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -240,6 +240,9 @@ public class PackageManagerService extends IPackageManager.Stub {
// This is where all application persistent data goes for secondary users.
final File mUserAppDataDir;
+ /** The location for ASEC container files on internal storage. */
+ final String mAsecInternalPath;
+
// This is the object monitoring the framework dir.
final FileObserver mFrameworkInstallObserver;
@@ -907,6 +910,7 @@ public class PackageManagerService extends IPackageManager.Stub {
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
+ mAsecInternalPath = new File(dataDir, "app-asec").getPath();
mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
@@ -1043,7 +1047,7 @@ public class PackageManagerService extends IPackageManager.Stub {
scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR,
scanMode | SCAN_NO_DEX, 0);
-
+
// Collect all system packages.
mSystemAppDir = new File(Environment.getRootDirectory(), "app");
mSystemInstallObserver = new AppDirObserver(
@@ -6479,6 +6483,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private boolean isAsecExternal(String cid) {
+ final String asecPath = PackageHelper.getSdFilesystem(cid);
+ return !asecPath.startsWith(mAsecInternalPath);
+ }
+
/**
* Extract the MountService "container ID" from the full code path of an
* .apk.
@@ -6517,7 +6526,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
AsecInstallArgs(String cid) {
- super(null, null, 0, null, null);
+ super(null, null, isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0, null, null);
this.cid = cid;
setCachePath(PackageHelper.getSdDir(cid));
}
@@ -8659,6 +8668,14 @@ public class PackageManagerService extends IPackageManager.Stub {
});
}
+ /**
+ * Called by MountService when the initial ASECs to scan are available.
+ * Should block until all the ASEC containers are finished being scanned.
+ */
+ public void scanAvailableAsecs() {
+ updateExternalMediaStatusInner(true, false);
+ }
+
/*
* Collect information of applications on external media, map them against
* existing containers and update information based on current mount status.
@@ -8793,7 +8810,11 @@ public class PackageManagerService extends IPackageManager.Stub {
continue;
}
// Parse package
- int parseFlags = PackageParser.PARSE_ON_SDCARD | mDefParseFlags;
+ int parseFlags = mDefParseFlags;
+ if (args.isExternal()) {
+ parseFlags |= PackageParser.PARSE_ON_SDCARD;
+ }
+
doGc = true;
synchronized (mInstallLock) {
final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags,
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index b3ac6f1..f460f9b 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -1173,8 +1173,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
if (!w.isVisibleOrAdding()) {
- Slog.i(TAG, " mSurface=" + w.mWinAnimator.mSurface + " reportDestroy="
- + w.mWinAnimator.mReportDestroySurface
+ Slog.i(TAG, " mSurface=" + w.mWinAnimator.mSurface
+ " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility
+ " policyVis=" + w.mPolicyVisibility + " attachHid=" + w.mAttachedHidden
+ " exiting=" + w.mExiting + " destroying=" + w.mDestroying);
@@ -2651,7 +2650,7 @@ public class WindowManagerService extends IWindowManager.Stub
int requestedHeight, int viewVisibility, int flags,
Rect outFrame, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
- boolean displayed = false;
+ boolean toBeDisplayed = false;
boolean inTouchMode;
boolean configChanged;
boolean surfaceChanged = false;
@@ -2754,7 +2753,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (viewVisibility == View.VISIBLE &&
(win.mAppToken == null || !win.mAppToken.clientHidden)) {
- displayed = !win.isVisibleLw();
+ toBeDisplayed = !win.isVisibleLw();
if (win.mExiting) {
winAnimator.cancelExitAnimationForNextAnimationLocked();
win.mExiting = false;
@@ -2766,7 +2765,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (oldVisibility == View.GONE) {
winAnimator.mEnterAnimationPending = true;
}
- if (displayed) {
+ if (toBeDisplayed) {
if (win.isDrawnLw() && okToDisplay()) {
winAnimator.applyEnterAnimationLocked();
}
@@ -2792,7 +2791,7 @@ public class WindowManagerService extends IWindowManager.Stub
if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
// To change the format, we need to re-build the surface.
winAnimator.destroySurfaceLocked();
- displayed = true;
+ toBeDisplayed = true;
surfaceChanged = true;
}
try {
@@ -2802,8 +2801,6 @@ public class WindowManagerService extends IWindowManager.Stub
Surface surface = winAnimator.createSurfaceLocked();
if (surface != null) {
outSurface.copyFrom(surface);
- winAnimator.mReportDestroySurface = false;
- winAnimator.mSurfacePendingDestroy = false;
if (SHOW_TRANSACTIONS) Slog.i(TAG,
" OUT SURFACE " + outSurface + ": copied");
} else {
@@ -2820,7 +2817,7 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(origId);
return 0;
}
- if (displayed) {
+ if (toBeDisplayed) {
focusMayChange = true;
}
if (win.mAttrs.type == TYPE_INPUT_METHOD
@@ -2845,11 +2842,10 @@ public class WindowManagerService extends IWindowManager.Stub
winAnimator.mEnterAnimationPending = false;
if (winAnimator.mSurface != null) {
if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
- + ": mExiting=" + win.mExiting
- + " mSurfacePendingDestroy=" + winAnimator.mSurfacePendingDestroy);
+ + ": mExiting=" + win.mExiting);
// If we are not currently running the exit animation, we
// need to see about starting one.
- if (!win.mExiting || winAnimator.mSurfacePendingDestroy) {
+ if (!win.mExiting) {
surfaceChanged = true;
// Try starting an animation; if there isn't one, we
// can destroy the surface right away.
@@ -2857,7 +2853,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
}
- if (!winAnimator.mSurfacePendingDestroy && win.isWinVisibleLw() &&
+ if (win.isWinVisibleLw() &&
winAnimator.applyAnimationLocked(transit, false)) {
focusMayChange = true;
win.mExiting = true;
@@ -2880,22 +2876,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- if (winAnimator.mSurface == null || (win.getAttrs().flags
- & WindowManager.LayoutParams.FLAG_KEEP_SURFACE_WHILE_ANIMATING) == 0
- || winAnimator.mSurfacePendingDestroy) {
- // We could be called from a local process, which
- // means outSurface holds its current surface. Ensure the
- // surface object is cleared, but we don't necessarily want
- // it actually destroyed at this point.
- winAnimator.mSurfacePendingDestroy = false;
- outSurface.release();
- if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win);
- } else if (winAnimator.mSurface != null) {
- if (DEBUG_VISIBILITY) Slog.i(TAG,
- "Keeping surface, will report destroy: " + win);
- winAnimator.mReportDestroySurface = true;
- outSurface.copyFrom(winAnimator.mSurface);
- }
+ outSurface.release();
+ if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win);
}
if (focusMayChange) {
@@ -2912,7 +2894,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean assignLayers = false;
if (imMayMove) {
- if (moveInputMethodWindowsIfNeededLocked(false) || displayed) {
+ if (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed) {
// Little hack here -- we -should- be able to rely on the
// function to return true if the IME has moved and needs
// its layer recomputed. However, if the IME was hidden
@@ -2934,7 +2916,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
configChanged = updateOrientationFromAppTokensLocked(false);
performLayoutAndPlaceSurfacesLocked();
- if (displayed && win.mIsWallpaper) {
+ if (toBeDisplayed && win.mIsWallpaper) {
updateWallpaperOffsetLocked(win, mAppDisplayWidth, mAppDisplayHeight, false);
}
if (win.mAppToken != null) {
@@ -2970,7 +2952,7 @@ public class WindowManagerService extends IWindowManager.Stub
Binder.restoreCallingIdentity(origId);
return (inTouchMode ? WindowManagerImpl.RELAYOUT_RES_IN_TOUCH_MODE : 0)
- | (displayed ? WindowManagerImpl.RELAYOUT_RES_FIRST_TIME : 0)
+ | (toBeDisplayed ? WindowManagerImpl.RELAYOUT_RES_FIRST_TIME : 0)
| (surfaceChanged ? WindowManagerImpl.RELAYOUT_RES_SURFACE_CHANGED : 0)
| (animating ? WindowManagerImpl.RELAYOUT_RES_ANIMATING : 0);
}
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 1fd80c2..e2a904f 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -679,8 +679,7 @@ final class WindowState implements WindowManagerPolicy.WindowState {
*/
boolean isVisibleOrAdding() {
final AppWindowToken atoken = mAppToken;
- return ((mHasSurface && !mWinAnimator.mReportDestroySurface)
- || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
+ return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
&& mPolicyVisibility && !mAttachedHidden
&& (atoken == null || !atoken.hiddenRequested)
&& !mExiting && !mDestroying;
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 5516dea..355db6e 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -71,8 +71,6 @@ class WindowStateAnimator {
Surface mSurface;
Surface mPendingDestroySurface;
- boolean mReportDestroySurface;
- boolean mSurfacePendingDestroy;
/**
* Set when we have changed the size of the surface, to know that
@@ -561,8 +559,6 @@ class WindowStateAnimator {
Surface createSurfaceLocked() {
if (mSurface == null) {
- mReportDestroySurface = false;
- mSurfacePendingDestroy = false;
if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
"createSurface " + this + ": mDrawState=DRAW_PENDING");
mDrawState = DRAW_PENDING;
@@ -694,7 +690,6 @@ class WindowStateAnimator {
mWin.mAppToken.startingDisplayed = false;
}
- mDrawState = NO_SURFACE;
if (mSurface != null) {
int i = mWin.mChildWindows.size();
@@ -704,17 +699,6 @@ class WindowStateAnimator {
c.mAttachedHidden = true;
}
- if (mReportDestroySurface) {
- mReportDestroySurface = false;
- mSurfacePendingDestroy = true;
- try {
- mWin.mClient.dispatchGetNewSurface();
- // We'll really destroy on the next time around.
- return;
- } catch (RemoteException e) {
- }
- }
-
try {
if (DEBUG_VISIBILITY) {
RuntimeException e = null;
@@ -760,6 +744,7 @@ class WindowStateAnimator {
mSurfaceShown = false;
mSurface = null;
mWin.mHasSurface =false;
+ mDrawState = NO_SURFACE;
}
}
@@ -1147,7 +1132,7 @@ class WindowStateAnimator {
}
} else {
if (DEBUG_ANIM) {
- Slog.v(TAG, "prepareSurface: No changes in animation for " + mWin);
+ // Slog.v(TAG, "prepareSurface: No changes in animation for " + mWin);
}
displayed = true;
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
index 414ae0d..0e75b80 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
@@ -210,28 +210,31 @@ public class GLTextureViewActivity extends Activity implements TextureView.Surfa
glEnableVertexAttribArray(attribTexCoords);
checkGlError();
- glUniform1i(uniformTexture, texture);
+ glUniform1i(uniformTexture, 0);
+ checkGlError();
+
+ // drawQuad
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+ checkGlError();
+
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+ checkGlError();
+
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
checkGlError();
while (!mFinished) {
checkCurrent();
- glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
- checkGlError();
-
glClear(GL_COLOR_BUFFER_BIT);
checkGlError();
- // drawQuad
- triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
- glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
- triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
- glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
- TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ checkGlError();
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
throw new RuntimeException("Cannot swap buffers");