Merge "Build Smoke tests against the SDK."
diff --git a/Android.mk b/Android.mk
index cacdee9..ab06058 100644
--- a/Android.mk
+++ b/Android.mk
@@ -60,7 +60,7 @@
## READ ME: ########################################################
LOCAL_SRC_FILES += \
core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl \
- core/java/android/accessibilityservice/IEventListener.aidl \
+ core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl \
core/java/android/accounts/IAccountManager.aidl \
core/java/android/accounts/IAccountManagerResponse.aidl \
core/java/android/accounts/IAccountAuthenticator.aidl \
@@ -111,6 +111,7 @@
core/java/android/database/IContentObserver.aidl \
core/java/android/hardware/ISerialManager.aidl \
core/java/android/hardware/input/IInputManager.aidl \
+ core/java/android/hardware/input/IInputDevicesChangedListener.aidl \
core/java/android/hardware/usb/IUsbManager.aidl \
core/java/android/net/IConnectivityManager.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 1cd5d01..939c117 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -126,6 +126,9 @@
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libRS_intermediates)
$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libRSDriver_intermediates)
$(call add-clean-step, rm -rf $(OUT_DIR)/host/$(HOST_PREBUILT_TAG)/obj/STATIC_LIBRARIES/libRS_intermediates)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/accessibilityservice/IEventListener.java)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/accessibilityservice/IEventListener.P)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/view/accessibility/IAccessibilityManager.P)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
# ************************************************
diff --git a/api/16.txt b/api/16.txt
index 8ff7675..cc67785 100644
--- a/api/16.txt
+++ b/api/16.txt
@@ -15190,11 +15190,11 @@
method public abstract void released();
}
- public class Vibrator {
- method public void cancel();
- method public boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long[], int);
+ public abstract class Vibrator {
+ method public abstract void cancel();
+ method public abstract boolean hasVibrator();
+ method public abstract void vibrate(long);
+ method public abstract void vibrate(long[], int);
}
public class WorkSource implements android.os.Parcelable {
@@ -24806,7 +24806,7 @@
enum_constant public static final android.webkit.ConsoleMessage.MessageLevel WARNING;
}
- public final class CookieManager {
+ public class CookieManager {
method public synchronized boolean acceptCookie();
method public static boolean allowFileSchemeCookies();
method public java.lang.String getCookie(java.lang.String);
@@ -24838,8 +24838,7 @@
method public abstract void onDownloadStart(java.lang.String, java.lang.String, java.lang.String, java.lang.String, long);
}
- public final class GeolocationPermissions {
- ctor public GeolocationPermissions();
+ public class GeolocationPermissions {
method public void allow(java.lang.String);
method public void clear(java.lang.String);
method public void clearAll();
@@ -24865,8 +24864,6 @@
public class JsResult {
method public final void cancel();
method public final void confirm();
- method protected final void wakeUp();
- field protected boolean mResult;
}
public class MimeTypeMap {
@@ -24959,7 +24956,7 @@
method public java.lang.String getUrl();
}
- public final class WebIconDatabase {
+ public class WebIconDatabase {
method public void close();
method public static android.webkit.WebIconDatabase getInstance();
method public void open(java.lang.String);
@@ -25128,8 +25125,7 @@
enum_constant public static final android.webkit.WebSettings.ZoomDensity MEDIUM;
}
- public final class WebStorage {
- ctor public WebStorage();
+ public class WebStorage {
method public void deleteAllData();
method public void deleteOrigin(java.lang.String);
method public static android.webkit.WebStorage getInstance();
@@ -25204,7 +25200,6 @@
method public java.lang.String getTitle();
method public java.lang.String getUrl();
method public deprecated int getVisibleTitleHeight();
- method public deprecated android.view.View getZoomControls();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -41832,7 +41827,7 @@
method public static void fail();
}
- public class AssertionFailedError extends java.lang.Error {
+ public class AssertionFailedError extends java.lang.AssertionError {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
@@ -41890,9 +41885,9 @@
method public synchronized void addListener(junit.framework.TestListener);
method public void endTest(junit.framework.Test);
method public synchronized int errorCount();
- method public synchronized java.util.Enumeration errors();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
method public synchronized int failureCount();
- method public synchronized java.util.Enumeration failures();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
method public synchronized void removeListener(junit.framework.TestListener);
method protected void run(junit.framework.TestCase);
method public synchronized int runCount();
@@ -41909,21 +41904,21 @@
public class TestSuite implements junit.framework.Test {
ctor public TestSuite();
- ctor public TestSuite(java.lang.Class, java.lang.String);
- ctor public TestSuite(java.lang.Class);
+ ctor public TestSuite(java.lang.Class<?>);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
ctor public TestSuite(java.lang.String);
method public void addTest(junit.framework.Test);
- method public void addTestSuite(java.lang.Class);
+ method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
method public int countTestCases();
- method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+ method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
method public java.lang.String getName();
- method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
method public void run(junit.framework.TestResult);
method public void runTest(junit.framework.Test, junit.framework.TestResult);
method public void setName(java.lang.String);
method public junit.framework.Test testAt(int);
method public int testCount();
- method public java.util.Enumeration tests();
+ method public java.util.Enumeration<junit.framework.Test> tests();
}
}
@@ -41946,7 +41941,7 @@
method protected static java.util.Properties getPreferences();
method public junit.framework.Test getTest(java.lang.String);
method public static boolean inVAJava();
- method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+ method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
method protected java.lang.String processArguments(java.lang.String[]);
method protected abstract void runFailed(java.lang.String);
method public static void savePreferences() throws java.io.IOException;
diff --git a/api/current.txt b/api/current.txt
index ee964f7..cd47769 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -534,6 +534,7 @@
field public static final int imeSubtypeLocale = 16843500; // 0x10102ec
field public static final int imeSubtypeMode = 16843501; // 0x10102ed
field public static final int immersive = 16843456; // 0x10102c0
+ field public static final int importantForAccessibility = 16843699; // 0x10103b3
field public static final int inAnimation = 16843127; // 0x1010177
field public static final int includeFontPadding = 16843103; // 0x101015f
field public static final int includeInGlobalSearch = 16843374; // 0x101026e
@@ -1993,11 +1994,23 @@
public abstract class AccessibilityService extends android.app.Service {
ctor public AccessibilityService();
+ method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public final android.os.IBinder onBind(android.content.Intent);
+ method protected void onGesture(int);
method public abstract void onInterrupt();
method protected void onServiceConnected();
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+ field public static final int GESTURE_CLOCKWISE_CIRCLE = 9; // 0x9
+ field public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10; // 0xa
+ field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
+ field public static final int GESTURE_SWIPE_DOWN_AND_UP = 8; // 0x8
+ field public static final int GESTURE_SWIPE_LEFT = 3; // 0x3
+ field public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5; // 0x5
+ field public static final int GESTURE_SWIPE_RIGHT = 4; // 0x4
+ field public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6; // 0x6
+ field public static final int GESTURE_SWIPE_UP = 1; // 0x1
+ field public static final int GESTURE_SWIPE_UP_AND_DOWN = 7; // 0x7
field public static final java.lang.String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService";
field public static final java.lang.String SERVICE_META_DATA = "android.accessibilityservice";
}
@@ -2022,6 +2035,7 @@
field public static final int FEEDBACK_HAPTIC = 2; // 0x2
field public static final int FEEDBACK_SPOKEN = 1; // 0x1
field public static final int FEEDBACK_VISUAL = 8; // 0x8
+ field public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
field public int eventTypes;
field public int feedbackType;
field public int flags;
@@ -3251,6 +3265,7 @@
method public android.app.DownloadManager.Request addRequestHeader(java.lang.String, java.lang.String);
method public void allowScanningByMediaScanner();
method public android.app.DownloadManager.Request setAllowedNetworkTypes(int);
+ method public android.app.DownloadManager.Request setAllowedOverMetered(boolean);
method public android.app.DownloadManager.Request setAllowedOverRoaming(boolean);
method public android.app.DownloadManager.Request setDescription(java.lang.CharSequence);
method public android.app.DownloadManager.Request setDestinationInExternalFilesDir(android.content.Context, java.lang.String, java.lang.String);
@@ -3686,6 +3701,7 @@
field public static final int PRIORITY_MIN = -2; // 0xfffffffe
field public static final int STREAM_DEFAULT = -1; // 0xffffffff
field public int audioStreamType;
+ field public android.widget.RemoteViews bigContentView;
field public android.app.PendingIntent contentIntent;
field public android.widget.RemoteViews contentView;
field public int defaults;
@@ -3708,6 +3724,18 @@
field public long when;
}
+ public static class Notification.BigPictureStyle {
+ ctor public Notification.BigPictureStyle(android.app.Notification.Builder);
+ method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap);
+ method public android.app.Notification build();
+ }
+
+ public static class Notification.BigTextStyle {
+ ctor public Notification.BigTextStyle(android.app.Notification.Builder);
+ method public android.app.Notification.BigTextStyle bigText(java.lang.CharSequence);
+ method public android.app.Notification build();
+ }
+
public static class Notification.Builder {
ctor public Notification.Builder(android.content.Context);
method public android.app.Notification.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
@@ -3737,6 +3765,7 @@
method public android.app.Notification.Builder setSubText(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence);
method public android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.app.Notification.Builder setUsesChronometer(boolean);
method public android.app.Notification.Builder setUsesIntruderAlert(boolean);
method public android.app.Notification.Builder setVibrate(long[]);
method public android.app.Notification.Builder setWhen(long);
@@ -4819,6 +4848,7 @@
method public android.content.ClipDescription getDescription();
method public android.content.ClipData.Item getItemAt(int);
method public int getItemCount();
+ method public static android.content.ClipData newHtmlText(java.lang.CharSequence, java.lang.CharSequence, java.lang.String);
method public static android.content.ClipData newIntent(java.lang.CharSequence, android.content.Intent);
method public static android.content.ClipData newPlainText(java.lang.CharSequence, java.lang.CharSequence);
method public static android.content.ClipData newRawUri(java.lang.CharSequence, android.net.Uri);
@@ -4829,10 +4859,15 @@
public static class ClipData.Item {
ctor public ClipData.Item(java.lang.CharSequence);
+ ctor public ClipData.Item(java.lang.CharSequence, java.lang.String);
ctor public ClipData.Item(android.content.Intent);
ctor public ClipData.Item(android.net.Uri);
ctor public ClipData.Item(java.lang.CharSequence, android.content.Intent, android.net.Uri);
+ ctor public ClipData.Item(java.lang.CharSequence, java.lang.String, android.content.Intent, android.net.Uri);
+ method public java.lang.String coerceToHtmlText(android.content.Context);
+ method public java.lang.CharSequence coerceToStyledText(android.content.Context);
method public java.lang.CharSequence coerceToText(android.content.Context);
+ method public java.lang.String getHtmlText();
method public android.content.Intent getIntent();
method public java.lang.CharSequence getText();
method public android.net.Uri getUri();
@@ -4850,6 +4885,7 @@
method public boolean hasMimeType(java.lang.String);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final java.lang.String MIMETYPE_TEXT_HTML = "text/html";
field public static final java.lang.String MIMETYPE_TEXT_INTENT = "text/vnd.android.intent";
field public static final java.lang.String MIMETYPE_TEXT_PLAIN = "text/plain";
field public static final java.lang.String MIMETYPE_TEXT_URILIST = "text/uri-list";
@@ -5687,6 +5723,7 @@
field public static final int EXTRA_DOCK_STATE_UNDOCKED = 0; // 0x0
field public static final java.lang.String EXTRA_DONT_KILL_APP = "android.intent.extra.DONT_KILL_APP";
field public static final java.lang.String EXTRA_EMAIL = "android.intent.extra.EMAIL";
+ field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
field public static final java.lang.String EXTRA_INITIAL_INTENTS = "android.intent.extra.INITIAL_INTENTS";
field public static final java.lang.String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
field public static final java.lang.String EXTRA_INTENT = "android.intent.extra.INTENT";
@@ -8492,6 +8529,7 @@
method public android.graphics.Paint.Align getTextAlign();
method public void getTextBounds(java.lang.String, int, int, android.graphics.Rect);
method public void getTextBounds(char[], int, int, android.graphics.Rect);
+ method public java.util.Locale getTextLocale();
method public void getTextPath(char[], int, int, float, float, android.graphics.Path);
method public void getTextPath(java.lang.String, int, int, float, float, android.graphics.Path);
method public float getTextScaleX();
@@ -8541,6 +8579,7 @@
method public void setStyle(android.graphics.Paint.Style);
method public void setSubpixelText(boolean);
method public void setTextAlign(android.graphics.Paint.Align);
+ method public void setTextLocale(java.util.Locale);
method public void setTextScaleX(float);
method public void setTextSize(float);
method public void setTextSkewX(float);
@@ -8979,6 +9018,8 @@
public class SurfaceTexture {
ctor public SurfaceTexture(int);
+ method public void attachToGLContext(int);
+ method public void detachFromGLContext();
method public long getTimestamp();
method public void getTransformMatrix(float[]);
method public void release();
@@ -9451,6 +9492,7 @@
method public static android.hardware.Camera open();
method public final void reconnect() throws java.io.IOException;
method public final void release();
+ method public void setAutoFocusMoveCallback(android.hardware.Camera.AutoFocusMoveCallback);
method public final void setDisplayOrientation(int);
method public final void setErrorCallback(android.hardware.Camera.ErrorCallback);
method public final void setFaceDetectionListener(android.hardware.Camera.FaceDetectionListener);
@@ -9486,6 +9528,10 @@
method public abstract void onAutoFocus(boolean, android.hardware.Camera);
}
+ public static abstract interface Camera.AutoFocusMoveCallback {
+ method public abstract void onAutoFocusMoving(boolean, android.hardware.Camera);
+ }
+
public static class Camera.CameraInfo {
ctor public Camera.CameraInfo();
field public static final int CAMERA_FACING_BACK = 0; // 0x0
@@ -9820,10 +9866,20 @@
package android.hardware.input {
public final class InputManager {
+ method public android.view.InputDevice getInputDevice(int);
+ method public int[] getInputDeviceIds();
+ method public void registerInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener, android.os.Handler);
+ method public void unregisterInputDeviceListener(android.hardware.input.InputManager.InputDeviceListener);
field public static final java.lang.String ACTION_QUERY_KEYBOARD_LAYOUTS = "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS";
field public static final java.lang.String META_DATA_KEYBOARD_LAYOUTS = "android.hardware.input.metadata.KEYBOARD_LAYOUTS";
}
+ public static abstract interface InputManager.InputDeviceListener {
+ method public abstract void onInputDeviceAdded(int);
+ method public abstract void onInputDeviceChanged(int);
+ method public abstract void onInputDeviceRemoved(int);
+ }
+
}
package android.hardware.usb {
@@ -10882,6 +10938,91 @@
field public static final int STOP_VIDEO_RECORDING = 3; // 0x3
}
+ public final class MediaCodec {
+ method public void configure(java.util.Map<java.lang.String, java.lang.Object>, android.view.Surface, android.media.MediaCrypto, int);
+ method public static android.media.MediaCodec createByCodecName(java.lang.String);
+ method public static android.media.MediaCodec createDecoderByType(java.lang.String);
+ method public static android.media.MediaCodec createEncoderByType(java.lang.String);
+ method public final int dequeueInputBuffer(long);
+ method public final int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long);
+ method public final void flush();
+ method public java.nio.ByteBuffer[] getInputBuffers();
+ method public java.nio.ByteBuffer[] getOutputBuffers();
+ method public final java.util.Map<java.lang.String, java.lang.Object> getOutputFormat();
+ method public final void queueInputBuffer(int, int, int, long, int);
+ method public final void queueSecureInputBuffer(int, int, int[], int[], int, byte[], byte[], int, long, int);
+ method public final void release();
+ method public final void releaseOutputBuffer(int, boolean);
+ method public final void start();
+ method public final void stop();
+ field public static int CONFIGURE_FLAG_ENCODE;
+ field public static final int FLAG_CODECCONFIG = 2; // 0x2
+ field public static final int FLAG_EOS = 4; // 0x4
+ field public static final int FLAG_SYNCFRAME = 1; // 0x1
+ field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd
+ field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
+ field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
+ field public static final int MODE_AES_CTR = 1; // 0x1
+ field public static final int MODE_UNENCRYPTED = 0; // 0x0
+ }
+
+ public static final class MediaCodec.BufferInfo {
+ ctor public MediaCodec.BufferInfo();
+ method public void set(int, int, long, int);
+ field public int flags;
+ field public int offset;
+ field public long presentationTimeUs;
+ field public int size;
+ }
+
+ public final class MediaCodecList {
+ method public static final int countCodecs();
+ method public static final android.media.MediaCodecList.CodecCapabilities getCodecCapabilities(int, java.lang.String);
+ method public static final java.lang.String getCodecName(int);
+ method public static final java.lang.String[] getSupportedTypes(int);
+ method public static final boolean isEncoder(int);
+ }
+
+ public static final class MediaCodecList.CodecCapabilities {
+ ctor public MediaCodecList.CodecCapabilities();
+ field public int[] colorFormats;
+ field public android.media.MediaCodecList.CodecProfileLevel[] profileLevels;
+ }
+
+ public static final class MediaCodecList.CodecProfileLevel {
+ ctor public MediaCodecList.CodecProfileLevel();
+ field public int level;
+ field public int profile;
+ }
+
+ public final class MediaCrypto {
+ ctor public MediaCrypto(byte[], byte[]);
+ method public static final boolean isCryptoSchemeSupported(byte[]);
+ method public final void release();
+ method public final boolean requiresSecureDecoderComponent(java.lang.String);
+ }
+
+ public final class MediaExtractor {
+ ctor public MediaExtractor();
+ method public boolean advance();
+ method public int countTracks();
+ method public int getSampleFlags();
+ method public long getSampleTime();
+ method public int getSampleTrackIndex();
+ method public java.util.Map<java.lang.String, java.lang.Object> getTrackFormat(int);
+ method public int readSampleData(java.nio.ByteBuffer, int);
+ method public final void release();
+ method public void seekTo(long);
+ method public void selectTrack(int);
+ method public final void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException;
+ method public final void setDataSource(java.lang.String, java.util.Map<java.lang.String, java.lang.String>);
+ method public final void setDataSource(java.lang.String);
+ method public final void setDataSource(java.io.FileDescriptor);
+ method public final void setDataSource(java.io.FileDescriptor, long, long);
+ field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
+ field public static final int SAMPLE_FLAG_SYNC = 1; // 0x1
+ }
+
public class MediaMetadataRetriever {
ctor public MediaMetadataRetriever();
method public java.lang.String extractMetadata(int);
@@ -11780,6 +11921,7 @@
method public deprecated boolean getBackgroundDataSetting();
method public android.net.NetworkInfo getNetworkInfo(int);
method public int getNetworkPreference();
+ method public boolean isActiveNetworkMetered();
method public static boolean isNetworkTypeValid(int);
method public boolean requestRouteToHost(int, int);
method public void setNetworkPreference(int);
@@ -12874,6 +13016,8 @@
method public static android.nfc.NfcAdapter getDefaultAdapter(android.content.Context);
method public boolean isEnabled();
method public boolean isNdefPushEnabled();
+ method public void setBeamPushUris(android.net.Uri[], android.app.Activity);
+ method public void setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity);
method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...);
method public void setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...);
method public void setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...);
@@ -12885,6 +13029,10 @@
field public static final java.lang.String EXTRA_TAG = "android.nfc.extra.TAG";
}
+ public static abstract interface NfcAdapter.CreateBeamUrisCallback {
+ method public abstract android.net.Uri[] createBeamUris(android.nfc.NfcEvent);
+ }
+
public static abstract interface NfcAdapter.CreateNdefMessageCallback {
method public abstract android.nfc.NdefMessage createNdefMessage(android.nfc.NfcEvent);
}
@@ -15469,11 +15617,11 @@
ctor public TransactionTooLargeException();
}
- public class Vibrator {
- method public void cancel();
- method public boolean hasVibrator();
- method public void vibrate(long);
- method public void vibrate(long[], int);
+ public abstract class Vibrator {
+ method public abstract void cancel();
+ method public abstract boolean hasVibrator();
+ method public abstract void vibrate(long);
+ method public abstract void vibrate(long[], int);
}
public class WorkSource implements android.os.Parcelable {
@@ -16822,7 +16970,7 @@
method public static android.net.Uri getLookupUri(android.content.ContentResolver, android.net.Uri);
method public static android.net.Uri getLookupUri(long, java.lang.String);
method public static android.net.Uri lookupContact(android.content.ContentResolver, android.net.Uri);
- method public static void markAsContacted(android.content.ContentResolver, long);
+ method public static deprecated void markAsContacted(android.content.ContentResolver, long);
method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri, boolean);
method public static java.io.InputStream openContactPhotoInputStream(android.content.ContentResolver, android.net.Uri);
field public static final android.net.Uri CONTENT_FILTER_URI;
@@ -16913,6 +17061,7 @@
public static final class ContactsContract.DataUsageFeedback {
ctor public ContactsContract.DataUsageFeedback();
+ field public static final android.net.Uri DELETE_USAGE_URI;
field public static final android.net.Uri FEEDBACK_URI;
field public static final java.lang.String USAGE_TYPE = "type";
field public static final java.lang.String USAGE_TYPE_CALL = "call";
@@ -17993,15 +18142,24 @@
method public static android.renderscript.Allocation createTyped(android.renderscript.RenderScript, android.renderscript.Type, int);
method public static android.renderscript.Allocation createTyped(android.renderscript.RenderScript, android.renderscript.Type);
method public void generateMipmaps();
+ method public int getBytesSize();
+ method public android.renderscript.Element getElement();
+ method public android.view.Surface getSurface();
method public android.renderscript.Type getType();
+ method public int getUsage();
+ method public void ioReceive();
+ method public void ioSend();
method public synchronized void resize(int);
method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
+ method public void setSurface(android.view.Surface);
method public void syncAll(int);
field public static final int USAGE_GRAPHICS_CONSTANTS = 8; // 0x8
field public static final int USAGE_GRAPHICS_RENDER_TARGET = 16; // 0x10
field public static final int USAGE_GRAPHICS_TEXTURE = 2; // 0x2
field public static final int USAGE_GRAPHICS_VERTEX = 4; // 0x4
+ field public static final int USAGE_IO_INPUT = 32; // 0x20
+ field public static final int USAGE_IO_OUTPUT = 64; // 0x40
field public static final int USAGE_SCRIPT = 1; // 0x1
}
@@ -18089,6 +18247,7 @@
method public static android.renderscript.Element F64_2(android.renderscript.RenderScript);
method public static android.renderscript.Element F64_3(android.renderscript.RenderScript);
method public static android.renderscript.Element F64_4(android.renderscript.RenderScript);
+ method public static android.renderscript.Element FONT(android.renderscript.RenderScript);
method public static android.renderscript.Element I16(android.renderscript.RenderScript);
method public static android.renderscript.Element I16_2(android.renderscript.RenderScript);
method public static android.renderscript.Element I16_3(android.renderscript.RenderScript);
@@ -18140,6 +18299,15 @@
method public static android.renderscript.Element U8_4(android.renderscript.RenderScript);
method public static android.renderscript.Element createPixel(android.renderscript.RenderScript, android.renderscript.Element.DataType, android.renderscript.Element.DataKind);
method public static android.renderscript.Element createVector(android.renderscript.RenderScript, android.renderscript.Element.DataType, int);
+ method public int getBytesSize();
+ method public android.renderscript.Element.DataKind getDataKind();
+ method public android.renderscript.Element.DataType getDataType();
+ method public android.renderscript.Element getSubElement(int);
+ method public int getSubElementArraySize(int);
+ method public int getSubElementCount();
+ method public java.lang.String getSubElementName(int);
+ method public int getSubElementOffsetBytes(int);
+ method public int getVectorSize();
method public boolean isCompatible(android.renderscript.Element);
method public boolean isComplex();
}
@@ -18172,8 +18340,10 @@
enum_constant public static final android.renderscript.Element.DataType MATRIX_2X2;
enum_constant public static final android.renderscript.Element.DataType MATRIX_3X3;
enum_constant public static final android.renderscript.Element.DataType MATRIX_4X4;
+ enum_constant public static final android.renderscript.Element.DataType NONE;
enum_constant public static final android.renderscript.Element.DataType RS_ALLOCATION;
enum_constant public static final android.renderscript.Element.DataType RS_ELEMENT;
+ enum_constant public static final android.renderscript.Element.DataType RS_FONT;
enum_constant public static final android.renderscript.Element.DataType RS_MESH;
enum_constant public static final android.renderscript.Element.DataType RS_PROGRAM_FRAGMENT;
enum_constant public static final android.renderscript.Element.DataType RS_PROGRAM_RASTER;
@@ -18486,12 +18656,18 @@
method public void bindConstants(android.renderscript.Allocation, int);
method public void bindSampler(android.renderscript.Sampler, int) throws java.lang.IllegalArgumentException;
method public void bindTexture(android.renderscript.Allocation, int) throws java.lang.IllegalArgumentException;
+ method public android.renderscript.Type getConstant(int);
+ method public int getConstantCount();
+ method public int getTextureCount();
+ method public java.lang.String getTextureName(int);
+ method public android.renderscript.Program.TextureType getTextureType(int);
}
public static class Program.BaseProgramBuilder {
ctor protected Program.BaseProgramBuilder(android.renderscript.RenderScript);
method public android.renderscript.Program.BaseProgramBuilder addConstant(android.renderscript.Type) throws java.lang.IllegalStateException;
method public android.renderscript.Program.BaseProgramBuilder addTexture(android.renderscript.Program.TextureType) throws java.lang.IllegalArgumentException;
+ method public android.renderscript.Program.BaseProgramBuilder addTexture(android.renderscript.Program.TextureType, java.lang.String) throws java.lang.IllegalArgumentException;
method public int getCurrentConstantIndex();
method public int getCurrentTextureIndex();
method protected void initProgram(android.renderscript.Program);
@@ -18547,6 +18723,8 @@
method public static android.renderscript.ProgramRaster CULL_BACK(android.renderscript.RenderScript);
method public static android.renderscript.ProgramRaster CULL_FRONT(android.renderscript.RenderScript);
method public static android.renderscript.ProgramRaster CULL_NONE(android.renderscript.RenderScript);
+ method public android.renderscript.ProgramRaster.CullMode getCullMode();
+ method public boolean isPointSpriteEnabled();
}
public static class ProgramRaster.Builder {
@@ -18569,6 +18747,15 @@
method public static android.renderscript.ProgramStore BLEND_ALPHA_DEPTH_TEST(android.renderscript.RenderScript);
method public static android.renderscript.ProgramStore BLEND_NONE_DEPTH_NONE(android.renderscript.RenderScript);
method public static android.renderscript.ProgramStore BLEND_NONE_DEPTH_TEST(android.renderscript.RenderScript);
+ method public android.renderscript.ProgramStore.BlendDstFunc getBlendDstFunc();
+ method public android.renderscript.ProgramStore.BlendSrcFunc getBlendSrcFunc();
+ method public android.renderscript.ProgramStore.DepthFunc getDepthFunc();
+ method public boolean isColorMaskAlphaEnabled();
+ method public boolean isColorMaskBlueEnabled();
+ method public boolean isColorMaskGreenEnabled();
+ method public boolean isColorMaskRedEnabled();
+ method public boolean isDepthMaskEnabled();
+ method public boolean isDitherEnabled();
}
public static final class ProgramStore.BlendDstFunc extends java.lang.Enum {
@@ -18621,6 +18808,8 @@
}
public class ProgramVertex extends android.renderscript.Program {
+ method public android.renderscript.Element getInput(int);
+ method public int getInputCount();
}
public static class ProgramVertex.Builder extends android.renderscript.Program.BaseProgramBuilder {
@@ -18758,6 +18947,11 @@
method public static android.renderscript.Sampler WRAP_LINEAR(android.renderscript.RenderScript);
method public static android.renderscript.Sampler WRAP_LINEAR_MIP_LINEAR(android.renderscript.RenderScript);
method public static android.renderscript.Sampler WRAP_NEAREST(android.renderscript.RenderScript);
+ method public float getAnisotropy();
+ method public android.renderscript.Sampler.Value getMagnification();
+ method public android.renderscript.Sampler.Value getMinification();
+ method public android.renderscript.Sampler.Value getWrapS();
+ method public android.renderscript.Sampler.Value getWrapT();
}
public static class Sampler.Builder {
@@ -19697,12 +19891,12 @@
method public final void testApplicationTestCaseSetUpProperly() throws java.lang.Exception;
}
- public class AssertionFailedError extends java.lang.Error {
+ public deprecated class AssertionFailedError extends java.lang.Error {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
- public class ComparisonFailure extends android.test.AssertionFailedError {
+ public deprecated class ComparisonFailure extends android.test.AssertionFailedError {
ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
}
@@ -19744,6 +19938,7 @@
ctor public InstrumentationTestSuite(android.app.Instrumentation);
ctor public InstrumentationTestSuite(java.lang.String, android.app.Instrumentation);
ctor public InstrumentationTestSuite(java.lang.Class, android.app.Instrumentation);
+ method public void addTestSuite(java.lang.Class);
}
public class IsolatedContext extends android.content.ContextWrapper {
@@ -20322,6 +20517,7 @@
}
public class Html {
+ method public static java.lang.String escapeHtml(java.lang.CharSequence);
method public static android.text.Spanned fromHtml(java.lang.String);
method public static android.text.Spanned fromHtml(java.lang.String, android.text.Html.ImageGetter, android.text.Html.TagHandler);
method public static java.lang.String toHtml(android.text.Spanned);
@@ -22269,6 +22465,7 @@
method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges();
method public java.lang.String getName();
method public int getSources();
+ method public android.os.Vibrator getVibrator();
method public boolean isVirtual();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -23195,6 +23392,7 @@
method public android.graphics.Canvas lockCanvas(android.graphics.Rect);
method protected final void onDraw(android.graphics.Canvas);
method public void setOpaque(boolean);
+ method public void setSurfaceTexture(android.graphics.SurfaceTexture);
method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener);
method public void setTransform(android.graphics.Matrix);
method public void unlockCanvasAndPost(android.graphics.Canvas);
@@ -23233,6 +23431,7 @@
ctor public View(android.content.Context);
ctor public View(android.content.Context, android.util.AttributeSet);
ctor public View(android.content.Context, android.util.AttributeSet, int);
+ method public void addChildrenForAccessibility(java.util.ArrayList<android.view.View>);
method public void addFocusables(java.util.ArrayList<android.view.View>, int);
method public void addFocusables(java.util.ArrayList<android.view.View>, int, int);
method public void addOnAttachStateChangeListener(android.view.View.OnAttachStateChangeListener);
@@ -23335,6 +23534,7 @@
method public int getHorizontalFadingEdgeLength();
method protected int getHorizontalScrollbarHeight();
method public int getId();
+ method public int getImportantForAccessibility();
method public boolean getKeepScreenOn();
method public android.view.KeyEvent.DispatcherState getKeyDispatcherState();
method public int getLayerType();
@@ -23368,6 +23568,7 @@
method public int getPaddingStart();
method public int getPaddingTop();
method public final android.view.ViewParent getParent();
+ method public android.view.ViewParent getParentForAccessibility();
method public float getPivotX();
method public float getPivotY();
method public int getResolvedLayoutDirection();
@@ -23523,6 +23724,7 @@
method public void onWindowSystemUiVisibilityChanged(int);
method protected void onWindowVisibilityChanged(int);
method protected boolean overScrollBy(int, int, int, int, int, int, int, int, boolean);
+ method public boolean performAccessibilityAction(int);
method public boolean performClick();
method public boolean performHapticFeedback(int);
method public boolean performHapticFeedback(int, int);
@@ -23594,6 +23796,7 @@
method public void setHorizontalScrollBarEnabled(boolean);
method public void setHovered(boolean);
method public void setId(int);
+ method public void setImportantForAccessibility(int);
method public void setKeepScreenOn(boolean);
method public void setLayerType(int, android.graphics.Paint);
method public void setLayoutDirection(int);
@@ -23668,6 +23871,14 @@
method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
+ field public static final int ACCESSIBILITY_FOCUS_BACKWARD = 4097; // 0x1001
+ field public static final int ACCESSIBILITY_FOCUS_DOWN = 4226; // 0x1082
+ field public static final int ACCESSIBILITY_FOCUS_FORWARD = 4098; // 0x1002
+ field public static final int ACCESSIBILITY_FOCUS_IN = 4100; // 0x1004
+ field public static final int ACCESSIBILITY_FOCUS_LEFT = 4113; // 0x1011
+ field public static final int ACCESSIBILITY_FOCUS_OUT = 4104; // 0x1008
+ field public static final int ACCESSIBILITY_FOCUS_RIGHT = 4162; // 0x1042
+ field public static final int ACCESSIBILITY_FOCUS_UP = 4129; // 0x1021
field public static final android.util.Property ALPHA;
field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
@@ -23689,6 +23900,7 @@
field protected static final int[] FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_STATE_SET;
field protected static final int[] FOCUSED_WINDOW_FOCUSED_STATE_SET;
+ field public static final int FOCUS_ACCESSIBILITY = 4096; // 0x1000
field public static final int FOCUS_BACKWARD = 1; // 0x1
field public static final int FOCUS_DOWN = 130; // 0x82
field public static final int FOCUS_FORWARD = 2; // 0x2
@@ -23697,6 +23909,9 @@
field public static final int FOCUS_UP = 33; // 0x21
field public static final int GONE = 8; // 0x8
field public static final int HAPTIC_FEEDBACK_ENABLED = 268435456; // 0x10000000
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
field public static final int INVISIBLE = 4; // 0x4
field public static final int KEEP_SCREEN_ON = 67108864; // 0x4000000
field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
@@ -24002,6 +24217,7 @@
method public android.view.View getFocusedChild();
method public android.view.animation.LayoutAnimationController getLayoutAnimation();
method public android.view.animation.Animation.AnimationListener getLayoutAnimationListener();
+ method public int getLayoutMode();
method public android.animation.LayoutTransition getLayoutTransition();
method public int getPersistentDrawingCache();
method public int indexOfChild(android.view.View);
@@ -24049,6 +24265,7 @@
method public void setDescendantFocusability(int);
method public void setLayoutAnimation(android.view.animation.LayoutAnimationController);
method public void setLayoutAnimationListener(android.view.animation.Animation.AnimationListener);
+ method public void setLayoutMode(int);
method public void setLayoutTransition(android.animation.LayoutTransition);
method public void setMotionEventSplittingEnabled(boolean);
method public void setOnHierarchyChangeListener(android.view.ViewGroup.OnHierarchyChangeListener);
@@ -24061,9 +24278,11 @@
method public void startViewTransition(android.view.View);
method public void updateViewLayout(android.view.View, android.view.ViewGroup.LayoutParams);
field protected static final int CLIP_TO_PADDING_MASK = 34; // 0x22
+ field public static final int COMPONENT_BOUNDS = 0; // 0x0
field public static final int FOCUS_AFTER_DESCENDANTS = 262144; // 0x40000
field public static final int FOCUS_BEFORE_DESCENDANTS = 131072; // 0x20000
field public static final int FOCUS_BLOCK_DESCENDANTS = 393216; // 0x60000
+ field public static final int LAYOUT_BOUNDS = 1; // 0x1
field public static final int PERSISTENT_ALL_CACHES = 3; // 0x3
field public static final int PERSISTENT_ANIMATION_CACHE = 1; // 0x1
field public static final int PERSISTENT_NO_CACHE = 0; // 0x0
@@ -24121,6 +24340,7 @@
method public abstract void focusableViewAvailable(android.view.View);
method public abstract boolean getChildVisibleRect(android.view.View, android.graphics.Rect, android.graphics.Point);
method public abstract android.view.ViewParent getParent();
+ method public abstract android.view.ViewParent getParentForAccessibility();
method public abstract void invalidateChild(android.view.View, android.graphics.Rect);
method public abstract android.view.ViewParent invalidateChildInParent(int[], android.graphics.Rect);
method public abstract boolean isLayoutRequested();
@@ -24522,6 +24742,8 @@
field public static final int TYPE_NOTIFICATION_STATE_CHANGED = 64; // 0x40
field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 32768; // 0x8000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 65536; // 0x10000
field public static final int TYPE_VIEW_CLICKED = 1; // 0x1
field public static final int TYPE_VIEW_FOCUSED = 8; // 0x8
field public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
@@ -24562,6 +24784,8 @@
method public void addChild(android.view.View, int);
method public int describeContents();
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String);
+ method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
+ method public android.view.accessibility.AccessibilityNodeInfo focusSearch(int);
method public int getActions();
method public void getBoundsInParent(android.graphics.Rect);
method public void getBoundsInScreen(android.graphics.Rect);
@@ -24573,6 +24797,7 @@
method public android.view.accessibility.AccessibilityNodeInfo getParent();
method public java.lang.CharSequence getText();
method public int getWindowId();
+ method public boolean isAccessibilityFocused();
method public boolean isCheckable();
method public boolean isChecked();
method public boolean isClickable();
@@ -24589,6 +24814,7 @@
method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
method public boolean performAction(int);
method public void recycle();
+ method public void setAccessibilityFocused(boolean);
method public void setBoundsInParent(android.graphics.Rect);
method public void setBoundsInScreen(android.graphics.Rect);
method public void setCheckable(boolean);
@@ -24610,16 +24836,23 @@
method public void setSource(android.view.View, int);
method public void setText(java.lang.CharSequence);
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int ACTION_ACCESSIBILITY_FOCUS = 16; // 0x10
+ field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 32; // 0x20
field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+ field public static final int ACTION_CLICK = 64; // 0x40
field public static final int ACTION_FOCUS = 1; // 0x1
field public static final int ACTION_SELECT = 4; // 0x4
field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
+ field public static final int FOCUS_INPUT = 1; // 0x1
}
public abstract class AccessibilityNodeProvider {
ctor public AccessibilityNodeProvider();
+ method public android.view.accessibility.AccessibilityNodeInfo accessibilityFocusSearch(int, int);
method public android.view.accessibility.AccessibilityNodeInfo createAccessibilityNodeInfo(int);
+ method public android.view.accessibility.AccessibilityNodeInfo findAccessibilitiyFocus(int);
method public java.util.List<android.view.accessibility.AccessibilityNodeInfo> findAccessibilityNodeInfosByText(java.lang.String, int);
method public boolean performAccessibilityAction(int, int);
}
@@ -25396,7 +25629,7 @@
enum_constant public static final android.webkit.ConsoleMessage.MessageLevel WARNING;
}
- public final class CookieManager {
+ public class CookieManager {
method public synchronized boolean acceptCookie();
method public static boolean allowFileSchemeCookies();
method public java.lang.String getCookie(java.lang.String);
@@ -25428,8 +25661,7 @@
method public abstract void onDownloadStart(java.lang.String, java.lang.String, java.lang.String, java.lang.String, long);
}
- public final class GeolocationPermissions {
- ctor public GeolocationPermissions();
+ public class GeolocationPermissions {
method public void allow(java.lang.String);
method public void clear(java.lang.String);
method public void clearAll();
@@ -25455,8 +25687,6 @@
public class JsResult {
method public final void cancel();
method public final void confirm();
- method protected final void wakeUp();
- field protected boolean mResult;
}
public class MimeTypeMap {
@@ -25549,7 +25779,7 @@
method public java.lang.String getUrl();
}
- public final class WebIconDatabase {
+ public class WebIconDatabase {
method public void close();
method public static android.webkit.WebIconDatabase getInstance();
method public void open(java.lang.String);
@@ -25718,8 +25948,7 @@
enum_constant public static final android.webkit.WebSettings.ZoomDensity MEDIUM;
}
- public final class WebStorage {
- ctor public WebStorage();
+ public class WebStorage {
method public void deleteAllData();
method public void deleteOrigin(java.lang.String);
method public static android.webkit.WebStorage getInstance();
@@ -25795,7 +26024,6 @@
method public java.lang.String getTitle();
method public java.lang.String getUrl();
method public deprecated int getVisibleTitleHeight();
- method public deprecated android.view.View getZoomControls();
method public void goBack();
method public void goBackOrForward(int);
method public void goForward();
@@ -42498,15 +42726,21 @@
method public static void assertTrue(boolean);
method public static void fail(java.lang.String);
method public static void fail();
+ method public static void failNotEquals(java.lang.String, java.lang.Object, java.lang.Object);
+ method public static void failNotSame(java.lang.String, java.lang.Object, java.lang.Object);
+ method public static void failSame(java.lang.String);
+ method public static java.lang.String format(java.lang.String, java.lang.Object, java.lang.Object);
}
- public class AssertionFailedError extends java.lang.Error {
+ public class AssertionFailedError extends java.lang.AssertionError {
ctor public AssertionFailedError();
ctor public AssertionFailedError(java.lang.String);
}
public class ComparisonFailure extends junit.framework.AssertionFailedError {
ctor public ComparisonFailure(java.lang.String, java.lang.String, java.lang.String);
+ method public java.lang.String getActual();
+ method public java.lang.String getExpected();
}
public abstract interface Protectable {
@@ -42558,9 +42792,9 @@
method public synchronized void addListener(junit.framework.TestListener);
method public void endTest(junit.framework.Test);
method public synchronized int errorCount();
- method public synchronized java.util.Enumeration errors();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> errors();
method public synchronized int failureCount();
- method public synchronized java.util.Enumeration failures();
+ method public synchronized java.util.Enumeration<junit.framework.TestFailure> failures();
method public synchronized void removeListener(junit.framework.TestListener);
method protected void run(junit.framework.TestCase);
method public synchronized int runCount();
@@ -42577,21 +42811,24 @@
public class TestSuite implements junit.framework.Test {
ctor public TestSuite();
- ctor public TestSuite(java.lang.Class, java.lang.String);
- ctor public TestSuite(java.lang.Class);
+ ctor public TestSuite(java.lang.Class<?>);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>, java.lang.String);
ctor public TestSuite(java.lang.String);
+ ctor public TestSuite(java.lang.Class<?>...);
+ ctor public TestSuite(java.lang.Class<? extends junit.framework.TestCase>[], java.lang.String);
method public void addTest(junit.framework.Test);
- method public void addTestSuite(java.lang.Class);
+ method public void addTestSuite(java.lang.Class<? extends junit.framework.TestCase>);
method public int countTestCases();
- method public static junit.framework.Test createTest(java.lang.Class, java.lang.String);
+ method public static junit.framework.Test createTest(java.lang.Class<?>, java.lang.String);
method public java.lang.String getName();
- method public static java.lang.reflect.Constructor getTestConstructor(java.lang.Class) throws java.lang.NoSuchMethodException;
+ method public static java.lang.reflect.Constructor<?> getTestConstructor(java.lang.Class<?>) throws java.lang.NoSuchMethodException;
method public void run(junit.framework.TestResult);
method public void runTest(junit.framework.Test, junit.framework.TestResult);
method public void setName(java.lang.String);
method public junit.framework.Test testAt(int);
method public int testCount();
- method public java.util.Enumeration tests();
+ method public java.util.Enumeration<junit.framework.Test> tests();
+ method public static junit.framework.Test warning(java.lang.String);
}
}
@@ -42608,13 +42845,13 @@
method public java.lang.String extractClassName(java.lang.String);
method public static java.lang.String getFilteredTrace(java.lang.Throwable);
method public static java.lang.String getFilteredTrace(java.lang.String);
- method public junit.runner.TestSuiteLoader getLoader();
+ method public deprecated junit.runner.TestSuiteLoader getLoader();
method public static java.lang.String getPreference(java.lang.String);
method public static int getPreference(java.lang.String, int);
method protected static java.util.Properties getPreferences();
method public junit.framework.Test getTest(java.lang.String);
- method public static boolean inVAJava();
- method protected java.lang.Class loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
+ method public static deprecated boolean inVAJava();
+ method protected java.lang.Class<?> loadSuiteClass(java.lang.String) throws java.lang.ClassNotFoundException;
method protected java.lang.String processArguments(java.lang.String[]);
method protected abstract void runFailed(java.lang.String);
method public static void savePreferences() throws java.io.IOException;
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index ddd7f7c..3da35d3 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -19,17 +19,22 @@
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
+import android.util.LocaleUtil;
import android.util.Log;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.os.HandlerCaller;
+import java.util.Locale;
+
/**
* An accessibility service runs in the background and receives callbacks by the system
* when {@link AccessibilityEvent}s are fired. Such events denote some state transition
@@ -202,12 +207,65 @@
* @see android.view.accessibility.AccessibilityManager
*/
public abstract class AccessibilityService extends Service {
+
+ /**
+ * The user has performed a swipe up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP = 1;
+
+ /**
+ * The user has performed a swipe down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN = 2;
+
+ /**
+ * The user has performed a swipe left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT = 3;
+
+ /**
+ * The user has performed a swipe right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT = 4;
+
+ /**
+ * The user has performed a swipe left and right gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
+
+ /**
+ * The user has performed a swipe right and left gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
+
+ /**
+ * The user has performed a swipe up and down gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
+
+ /**
+ * The user has performed a swipe down and up gesture on the touch screen.
+ */
+ public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
+
+ /**
+ * The user has performed a clockwise circle gesture on the touch screen.
+ */
+ public static final int GESTURE_CLOCKWISE_CIRCLE = 9;
+
+ /**
+ * The user has performed a counter clockwise circle gesture on the touch screen.
+ */
+ public static final int GESTURE_COUNTER_CLOCKWISE_CIRCLE = 10;
+
/**
* The {@link Intent} that must be declared as handled by the service.
*/
public static final String SERVICE_INTERFACE =
"android.accessibilityservice.AccessibilityService";
+ private static final int UNDEFINED = -1;
+
/**
* Name under which an AccessibilityService component publishes information
* about itself. This meta-data must reference an XML resource containing an
@@ -233,12 +291,15 @@
public void onInterrupt();
public void onServiceConnected();
public void onSetConnectionId(int connectionId);
+ public void onGesture(int gestureId);
}
private int mConnectionId;
private AccessibilityServiceInfo mInfo;
+ private int mLayoutDirection;
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -264,6 +325,106 @@
}
/**
+ * Called by the system when the user performs a specific gesture on the
+ * touch screen.
+ *
+ * @param gestureId The unique id of the performed gesture.
+ *
+ * @see #GESTURE_SWIPE_UP
+ * @see #GESTURE_SWIPE_DOWN
+ * @see #GESTURE_SWIPE_LEFT
+ * @see #GESTURE_SWIPE_RIGHT
+ * @see #GESTURE_SWIPE_UP_AND_DOWN
+ * @see #GESTURE_SWIPE_DOWN_AND_UP
+ * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+ * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+ * @see #GESTURE_CLOCKWISE_CIRCLE
+ * @see #GESTURE_COUNTER_CLOCKWISE_CIRCLE
+ */
+ protected void onGesture(int gestureId) {
+ // TODO: Describe the default gesture processing in the javaDoc once it is finalized.
+
+ // Cache the id to avoid locking
+ final int connectionId = mConnectionId;
+ if (connectionId == UNDEFINED) {
+ throw new IllegalStateException("AccessibilityService not connected."
+ + " Did you receive a call of onServiceConnected()?");
+ }
+ AccessibilityNodeInfo root = AccessibilityInteractionClient.getInstance()
+ .findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
+ AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
+ if (root == null) {
+ return;
+ }
+ AccessibilityNodeInfo current = root.findFocus(View.FOCUS_ACCESSIBILITY);
+ if (current == null) {
+ current = root;
+ }
+ AccessibilityNodeInfo next = null;
+ switch (gestureId) {
+ case GESTURE_SWIPE_UP: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_OUT);
+ } break;
+ case GESTURE_SWIPE_DOWN: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_IN);
+ } break;
+ case GESTURE_SWIPE_LEFT: {
+ if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+ } else { // LAYOUT_DIRECTION_RTL
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+ }
+ } break;
+ case GESTURE_SWIPE_RIGHT: {
+ if (mLayoutDirection == View.LAYOUT_DIRECTION_LTR) {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_FORWARD);
+ } else { // LAYOUT_DIRECTION_RTL
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_BACKWARD);
+ }
+ } break;
+ case GESTURE_SWIPE_UP_AND_DOWN: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_UP);
+ } break;
+ case GESTURE_SWIPE_DOWN_AND_UP: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_DOWN);
+ } break;
+ case GESTURE_SWIPE_LEFT_AND_RIGHT: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_LEFT);
+ } break;
+ case GESTURE_SWIPE_RIGHT_AND_LEFT: {
+ next = current.focusSearch(View.ACCESSIBILITY_FOCUS_RIGHT);
+ } break;
+ }
+ if (next != null && !next.equals(current)) {
+ next.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+ }
+ }
+
+ /**
+ * Gets the an {@link AccessibilityServiceInfo} describing this
+ * {@link AccessibilityService}. This method is useful if one wants
+ * to change some of the dynamically configurable properties at
+ * runtime.
+ *
+ * @return The accessibility service info.
+ *
+ * @see AccessibilityNodeInfo
+ */
+ public final AccessibilityServiceInfo getServiceInfo() {
+ IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.getServiceInfo();
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while getting AccessibilityServiceInfo", re);
+ }
+ }
+ return null;
+ }
+
+ /**
* Sets the {@link AccessibilityServiceInfo} that describes this service.
* <p>
* Note: You can call this method any time but the info will be picked up after
@@ -287,19 +448,33 @@
if (mInfo != null && connection != null) {
try {
connection.setServiceInfo(mInfo);
+ mInfo = null;
+ AccessibilityInteractionClient.getInstance().clearCache();
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while setting AccessibilityServiceInfo", re);
}
}
}
+ @Override
+ public void onCreate() {
+ Locale locale = getResources().getConfiguration().locale;
+ mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration configuration) {
+ super.onConfigurationChanged(configuration);
+ mLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(configuration.locale);
+ }
+
/**
* Implement to return the implementation of the internal accessibility
* service interface.
*/
@Override
public final IBinder onBind(Intent intent) {
- return new IEventListenerWrapper(this, getMainLooper(), new Callbacks() {
+ return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
@Override
public void onServiceConnected() {
AccessibilityService.this.onServiceConnected();
@@ -319,14 +494,19 @@
public void onSetConnectionId( int connectionId) {
mConnectionId = connectionId;
}
+
+ @Override
+ public void onGesture(int gestureId) {
+ AccessibilityService.this.onGesture(gestureId);
+ }
});
}
/**
- * Implements the internal {@link IEventListener} interface to convert
+ * Implements the internal {@link IAccessibilityServiceClient} interface to convert
* incoming calls to it back to calls on an {@link AccessibilityService}.
*/
- static class IEventListenerWrapper extends IEventListener.Stub
+ static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
implements HandlerCaller.Callback {
static final int NO_ID = -1;
@@ -334,12 +514,14 @@
private static final int DO_SET_SET_CONNECTION = 10;
private static final int DO_ON_INTERRUPT = 20;
private static final int DO_ON_ACCESSIBILITY_EVENT = 30;
+ private static final int DO_ON_GESTURE = 40;
private final HandlerCaller mCaller;
private final Callbacks mCallback;
- public IEventListenerWrapper(Context context, Looper looper, Callbacks callback) {
+ public IAccessibilityServiceClientWrapper(Context context, Looper looper,
+ Callbacks callback) {
mCallback = callback;
mCaller = new HandlerCaller(context, looper, this);
}
@@ -360,6 +542,11 @@
mCaller.sendMessage(message);
}
+ public void onGesture(int gestureId) {
+ Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
+ mCaller.sendMessage(message);
+ }
+
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT :
@@ -387,6 +574,10 @@
mCallback.onSetConnectionId(AccessibilityInteractionClient.NO_ID);
}
return;
+ case DO_ON_GESTURE :
+ final int gestureId = message.arg1;
+ mCallback.onGesture(gestureId);
+ return;
default :
Log.w(LOG_TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8e53431..e77ed9a 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -25,11 +25,13 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import org.xmlpull.v1.XmlPullParser;
@@ -101,6 +103,37 @@
public static final int DEFAULT = 0x0000001;
/**
+ * If this flag is set the system will regard views that are not important
+ * for accessibility in addition to the ones that are important for accessibility.
+ * That is, views that are marked as not important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_NO} and views that are marked as
+ * potentially important for accessibility via
+ * {@link View#IMPORTANT_FOR_ACCESSIBILITY_AUTO} for which the system has determined
+ * that are not important for accessibility, are both reported while querying the
+ * window content and also the accessibility service will receive accessibility events
+ * from them.
+ * <p>
+ * <strong>Note:</strong> For accessibility services targeting API version
+ * {@link Build.VERSION_CODES#JELLY_BEAN} or higher this flag has to be explicitly
+ * set for the system to regard views that are not important for accessibility. For
+ * accessibility services targeting API version lower than
+ * {@link Build.VERSION_CODES#JELLY_BEAN} this flag is ignored and all views are
+ * regarded for accessibility purposes.
+ * </p>
+ * <p>
+ * Usually views not important for accessibility are layout managers that do not
+ * react to user actions, do not draw any content, and do not have any special
+ * semantics in the context of the screen content. For example, a three by three
+ * grid can be implemented as three horizontal linear layouts and one vertical,
+ * or three vertical linear layouts and one horizontal, or one grid layout, etc.
+ * In this context the actual layout mangers used to achieve the grid configuration
+ * are not important, rather it is important that there are nine evenly distributed
+ * elements.
+ * </p>
+ */
+ public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x0000002;
+
+ /**
* The event types an {@link AccessibilityService} is interested in.
* <p>
* <strong>Can be dynamically set at runtime.</strong>
@@ -165,6 +198,7 @@
* <strong>Can be dynamically set at runtime.</strong>
* </p>
* @see #DEFAULT
+ * @see #INCLUDE_NOT_IMPORTANT_VIEWS
*/
public int flags;
@@ -561,6 +595,8 @@
switch (flag) {
case DEFAULT:
return "DEFAULT";
+ case INCLUDE_NOT_IMPORTANT_VIEWS:
+ return "REGARD_VIEWS_NOT_IMPORTANT_FOR_ACCESSIBILITY";
default:
return null;
}
diff --git a/core/java/android/accessibilityservice/IEventListener.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
similarity index 86%
rename from core/java/android/accessibilityservice/IEventListener.aidl
rename to core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 5536b3c..588728c 100644
--- a/core/java/android/accessibilityservice/IEventListener.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -20,15 +20,17 @@
import android.view.accessibility.AccessibilityEvent;
/**
- * Top-level interface to accessibility service component (implemented in Service).
+ * Top-level interface to an accessibility service component.
*
* @hide
*/
- oneway interface IEventListener {
+ oneway interface IAccessibilityServiceClient {
void setConnection(in IAccessibilityServiceConnection connection, int connectionId);
void onAccessibilityEvent(in AccessibilityEvent event);
void onInterrupt();
+
+ void onGesture(int gestureId);
}
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 8d17325..30da9db 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -41,13 +41,13 @@
* to start from the root.
* @param interactionId The id of the interaction for matching with the callback result.
* @param callback Callback which to receive the result.
+ * @param flags Additional flags.
* @param threadId The id of the calling thread.
- * @param prefetchFlags flags to guide prefetching.
* @return The current window scale, where zero means a failure.
*/
float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long threadId, int prefetchFlags);
+ IAccessibilityInteractionConnectionCallback callback, int flags, long threadId);
/**
* Finds {@link android.view.accessibility.AccessibilityNodeInfo}s by View text.
@@ -94,6 +94,48 @@
long threadId);
/**
+ * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the specified
+ * focus type. The search is performed in the window whose id is specified and starts from
+ * the node whose accessibility id is specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param focusType The type of focus to find.
+ * @param interactionId The id of the interaction for matching with the callback result.
+ * @param callback Callback which to receive the result.
+ * @param threadId The id of the calling thread.
+ * @return The current window scale, where zero means a failure.
+ */
+ float findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
+
+ /**
+ * Finds an {@link android.view.accessibility.AccessibilityNodeInfo} to take accessibility
+ * focus in the given direction. The search is performed in the window whose id is
+ * specified and starts from the node whose accessibility id is specified.
+ *
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param direction The direction in which to search for focusable.
+ * @param interactionId The id of the interaction for matching with the callback result.
+ * @param callback Callback which to receive the result.
+ * @param threadId The id of the calling thread.
+ * @return The current window scale, where zero means a failure.
+ */
+ float focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);
+
+ /**
* Performs an accessibility action on an
* {@link android.view.accessibility.AccessibilityNodeInfo}.
*
@@ -113,4 +155,9 @@
boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
int action, int interactionId, IAccessibilityInteractionConnectionCallback callback,
long threadId);
+
+ /**
+ * @return The associated accessibility service info.
+ */
+ AccessibilityServiceInfo getServiceInfo();
}
diff --git a/core/java/android/accessibilityservice/UiTestAutomationBridge.java b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
index a898c3f..c840bd6 100644
--- a/core/java/android/accessibilityservice/UiTestAutomationBridge.java
+++ b/core/java/android/accessibilityservice/UiTestAutomationBridge.java
@@ -17,7 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.AccessibilityService.Callbacks;
-import android.accessibilityservice.AccessibilityService.IEventListenerWrapper;
+import android.accessibilityservice.AccessibilityService.IAccessibilityServiceClientWrapper;
import android.content.Context;
import android.os.HandlerThread;
import android.os.Looper;
@@ -66,7 +66,7 @@
private volatile int mConnectionId = AccessibilityInteractionClient.NO_ID;
- private IEventListenerWrapper mListener;
+ private IAccessibilityServiceClientWrapper mListener;
private AccessibilityEvent mLastEvent;
@@ -133,7 +133,7 @@
mHandlerThread.start();
Looper looper = mHandlerThread.getLooper();
- mListener = new IEventListenerWrapper(null, looper, new Callbacks() {
+ mListener = new IAccessibilityServiceClientWrapper(null, looper, new Callbacks() {
@Override
public void onServiceConnected() {
/* do nothing */
@@ -175,6 +175,11 @@
mLock.notifyAll();
}
}
+
+ @Override
+ public void onGesture(int gestureId) {
+ /* do nothing */
+ }
});
final IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
@@ -252,6 +257,7 @@
public AccessibilityEvent executeCommandAndWaitForAccessibilityEvent(Runnable command,
Predicate<AccessibilityEvent> predicate, long timeoutMillis)
throws TimeoutException, Exception {
+ // TODO: This is broken - remove from here when finalizing this as public APIs.
synchronized (mLock) {
// Prepare to wait for an event.
mWaitingForEventDelivery = true;
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index c5a4171..f9fa444 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -420,11 +420,7 @@
if (duration < 0) {
throw new IllegalArgumentException("duration must be a value of zero or greater");
}
- for (Node node : mNodes) {
- // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
- // insert "play-after" delays
- node.animation.setDuration(duration);
- }
+ // Just record the value for now - it will be used later when the AnimatorSet starts
mDuration = duration;
return this;
}
@@ -456,6 +452,14 @@
mTerminated = false;
mStarted = true;
+ if (mDuration >= 0) {
+ // If the duration was set on this AnimatorSet, pass it along to all child animations
+ for (Node node : mNodes) {
+ // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+ // insert "play-after" delays
+ node.animation.setDuration(mDuration);
+ }
+ }
// First, sort the nodes (if necessary). This will ensure that sortedNodes
// contains the animation nodes in the correct order.
sortNodes();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 227900e..1c820dc 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4656,7 +4656,7 @@
/**
* Print the Activity's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity <activity_component_name>".
+ * you run "adb shell dumpsys activity <activity_component_name>".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a3fdf3e..7e1589f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1000,7 +1000,7 @@
}
return true;
}
-
+
case GOING_TO_SLEEP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
goingToSleep();
@@ -1015,6 +1015,13 @@
return true;
}
+ case SET_LOCK_SCREEN_SHOWN_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ setLockScreenShown(data.readInt() != 0);
+ reply.writeNoException();
+ return true;
+ }
+
case SET_DEBUG_APP_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String pn = data.readString();
@@ -2912,6 +2919,17 @@
data.recycle();
reply.recycle();
}
+ public void setLockScreenShown(boolean shown) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(shown ? 1 : 0);
+ mRemote.transact(SET_LOCK_SCREEN_SHOWN_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
public void setDebugApp(
String packageName, boolean waitForDebugger, boolean persistent)
throws RemoteException
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 138a88f..0645aa9 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -82,7 +82,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserId;
-import android.os.Vibrator;
+import android.os.SystemVibrator;
import android.os.storage.StorageManager;
import android.telephony.TelephonyManager;
import android.content.ClipboardManager;
@@ -455,7 +455,7 @@
registerService(VIBRATOR_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return new Vibrator();
+ return new SystemVibrator();
}});
registerService(WALLPAPER_SERVICE, WALLPAPER_FETCHER);
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index dd58397..55f29e6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -349,6 +349,7 @@
private String mMimeType;
private boolean mRoamingAllowed = true;
private int mAllowedNetworkTypes = ~0; // default to all network types allowed
+ private boolean mAllowedOverMetered = true;
private boolean mIsVisibleInDownloadsUi = true;
private boolean mScannable = false;
private boolean mUseSystemCache = false;
@@ -609,8 +610,11 @@
}
/**
- * Restrict the types of networks over which this download may proceed. By default, all
- * network types are allowed.
+ * Restrict the types of networks over which this download may proceed.
+ * By default, all network types are allowed. Consider using
+ * {@link #setAllowedOverMetered(boolean)} instead, since it's more
+ * flexible.
+ *
* @param flags any combination of the NETWORK_* bit flags.
* @return this object
*/
@@ -620,6 +624,17 @@
}
/**
+ * Set whether this download may proceed over a metered network
+ * connection. By default, metered networks are allowed.
+ *
+ * @see ConnectivityManager#isActiveNetworkMetered()
+ */
+ public Request setAllowedOverMetered(boolean allow) {
+ mAllowedOverMetered = allow;
+ return this;
+ }
+
+ /**
* Set whether this download may proceed over a roaming connection. By default, roaming is
* allowed.
* @param allowed whether to allow a roaming connection to be used
@@ -672,6 +687,7 @@
putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
+ // TODO: add COLUMN_ALLOW_METERED and persist
values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index c71b186..3fc2280 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -205,7 +205,8 @@
// Note: probably don't want to allow applications access to these.
public void goingToSleep() throws RemoteException;
public void wakingUp() throws RemoteException;
-
+ public void setLockScreenShown(boolean shown) throws RemoteException;
+
public void unhandledBack() throws RemoteException;
public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException;
public void setDebugApp(
@@ -588,4 +589,5 @@
int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144;
int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
+ int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 04c64a0..5cce25f 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -196,10 +196,9 @@
public RemoteViews intruderView;
/**
- * A larger version of {@link #contentView}, giving the Notification an
+ * A large-format version of {@link #contentView}, giving the Notification an
* opportunity to show more detail. The system UI may choose to show this
* instead of the normal content view at its discretion.
- * @hide
*/
public RemoteViews bigContentView;
@@ -987,8 +986,6 @@
}
/**
- * @hide
- *
* Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp.
*
* @see Notification#when
@@ -1609,23 +1606,21 @@
}
/**
- * @hide because this API is still very rough
+ * Helper class for generating large-format notifications that include a large image attachment.
*
- * This is a "rebuilder": It consumes a Builder object and modifies its output.
- *
- * This represents the "big picture" style notification, with a large Bitmap atop the usual notification.
- *
- * Usage:
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
* <pre class="prettyprint">
* Notification noti = new Notification.BigPictureStyle(
* new Notification.Builder()
- * .setContentTitle("New mail from " + sender.toString())
+ * .setContentTitle("New photo from " + sender.toString())
* .setContentText(subject)
- * .setSmallIcon(R.drawable.new_mail)
+ * .setSmallIcon(R.drawable.new_post)
* .setLargeIcon(aBitmap))
* .bigPicture(aBigBitmap)
* .build();
* </pre>
+ *
+ * @see Notification#bigContentView
*/
public static class BigPictureStyle {
private Builder mBuilder;
@@ -1656,13 +1651,9 @@
}
/**
- * @hide because this API is still very rough
+ * Helper class for generating large-format notifications that include a lot of text.
*
- * This is a "rebuilder": It consumes a Builder object and modifies its output.
- *
- * This represents the "big text" style notification, with more area for the main content text to be read in its entirety.
- *
- * Usage:
+ * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so:
* <pre class="prettyprint">
* Notification noti = new Notification.BigPictureStyle(
* new Notification.Builder()
@@ -1673,6 +1664,8 @@
* .bigText(aVeryLongString)
* .build();
* </pre>
+ *
+ * @see Notification#bigContentView
*/
public static class BigTextStyle {
private Builder mBuilder;
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index 207ae76..cb43d4c 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -666,8 +666,8 @@
/**
* Print the Service's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity service <yourservicename>".
- * This is distinct from "dumpsys <servicename>", which only works for
+ * you run "adb shell dumpsys activity service <yourservicename>".
+ * This is distinct from "dumpsys <servicename>", which only works for
* named system services and which invokes the {@link IBinder#dump} method
* on the {@link IBinder} interface registered with ServiceManager.
*
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index a655dd4..1866830 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -21,7 +21,12 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Html;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
+import android.text.style.URLSpan;
import android.util.Log;
import java.io.FileInputStream;
@@ -144,6 +149,8 @@
public class ClipData implements Parcelable {
static final String[] MIMETYPES_TEXT_PLAIN = new String[] {
ClipDescription.MIMETYPE_TEXT_PLAIN };
+ static final String[] MIMETYPES_TEXT_HTML = new String[] {
+ ClipDescription.MIMETYPE_TEXT_HTML };
static final String[] MIMETYPES_TEXT_URILIST = new String[] {
ClipDescription.MIMETYPE_TEXT_URILIST };
static final String[] MIMETYPES_TEXT_INTENT = new String[] {
@@ -176,6 +183,7 @@
*/
public static class Item {
final CharSequence mText;
+ final String mHtmlText;
final Intent mIntent;
final Uri mUri;
@@ -184,6 +192,20 @@
*/
public Item(CharSequence text) {
mText = text;
+ mHtmlText = null;
+ mIntent = null;
+ mUri = null;
+ }
+
+ /**
+ * Create an Item consisting of a single block of (possibly styled) text,
+ * with an alternative HTML formatted representation. You <em>must</em>
+ * supply a plain text representation in addition to HTML text; coercion
+ * will not be done from HTML formated text into plain text.
+ */
+ public Item(CharSequence text, String htmlText) {
+ mText = text;
+ mHtmlText = htmlText;
mIntent = null;
mUri = null;
}
@@ -193,6 +215,7 @@
*/
public Item(Intent intent) {
mText = null;
+ mHtmlText = null;
mIntent = intent;
mUri = null;
}
@@ -202,16 +225,35 @@
*/
public Item(Uri uri) {
mText = null;
+ mHtmlText = null;
mIntent = null;
mUri = uri;
}
/**
* Create a complex Item, containing multiple representations of
- * text, intent, and/or URI.
+ * text, Intent, and/or URI.
*/
public Item(CharSequence text, Intent intent, Uri uri) {
mText = text;
+ mHtmlText = null;
+ mIntent = intent;
+ mUri = uri;
+ }
+
+ /**
+ * Create a complex Item, containing multiple representations of
+ * text, HTML text, Intent, and/or URI. If providing HTML text, you
+ * <em>must</em> supply a plain text representation as well; coercion
+ * will not be done from HTML formated text into plain text.
+ */
+ public Item(CharSequence text, String htmlText, Intent intent, Uri uri) {
+ if (htmlText != null && text == null) {
+ throw new IllegalArgumentException(
+ "Plain text must be supplied if HTML text is supplied");
+ }
+ mText = text;
+ mHtmlText = htmlText;
mIntent = intent;
mUri = uri;
}
@@ -224,6 +266,13 @@
}
/**
+ * Retrieve the raw HTML text contained in this Item.
+ */
+ public String getHtmlText() {
+ return mHtmlText;
+ }
+
+ /**
* Retrieve the raw Intent contained in this Item.
*/
public Intent getIntent() {
@@ -250,7 +299,7 @@
* the content provider does not supply a text representation, return
* the raw URI as a string.
* <li> If {@link #getIntent} is non-null, convert that to an intent:
- * URI and returnit.
+ * URI and return it.
* <li> Otherwise, return an empty string.
* </ul>
*
@@ -261,12 +310,14 @@
//BEGIN_INCLUDE(coerceToText)
public CharSequence coerceToText(Context context) {
// If this Item has an explicit textual value, simply return that.
- if (mText != null) {
- return mText;
+ CharSequence text = getText();
+ if (text != null) {
+ return text;
}
// If this Item has a URI value, try using that.
- if (mUri != null) {
+ Uri uri = getUri();
+ if (uri != null) {
// First see if the URI can be opened as a plain text stream
// (of any sub-type). If so, this is the best textual
@@ -275,7 +326,7 @@
try {
// Ask for a stream of the desired type.
AssetFileDescriptor descr = context.getContentResolver()
- .openTypedAssetFileDescriptor(mUri, "text/*", null);
+ .openTypedAssetFileDescriptor(uri, "text/*", null);
stream = descr.createInputStream();
InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
@@ -308,13 +359,14 @@
// If we couldn't open the URI as a stream, then the URI itself
// probably serves fairly well as a textual representation.
- return mUri.toString();
+ return uri.toString();
}
// Finally, if all we have is an Intent, then we can just turn that
// into text. Not the most user-friendly thing, but it's something.
- if (mIntent != null) {
- return mIntent.toUri(Intent.URI_INTENT_SCHEME);
+ Intent intent = getIntent();
+ if (intent != null) {
+ return intent.toUri(Intent.URI_INTENT_SCHEME);
}
// Shouldn't get here, but just in case...
@@ -322,6 +374,210 @@
}
//END_INCLUDE(coerceToText)
+ /**
+ * Like {@link #coerceToHtmlText(Context)}, but any text that would
+ * be returned as HTML formatting will be returned as text with
+ * style spans.
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's textual representation.
+ */
+ public CharSequence coerceToStyledText(Context context) {
+ CharSequence text = getText();
+ if (text instanceof Spanned) {
+ return text;
+ }
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ try {
+ CharSequence newText = Html.fromHtml(htmlText);
+ if (newText != null) {
+ return newText;
+ }
+ } catch (RuntimeException e) {
+ // If anything bad happens, we'll fall back on the plain text.
+ }
+ }
+
+ if (text != null) {
+ return text;
+ }
+ return coerceToHtmlOrStyledText(context, true);
+ }
+
+ /**
+ * Turn this item into HTML text, regardless of the type of data it
+ * actually contains.
+ *
+ * <p>The algorithm for deciding what text to return is:
+ * <ul>
+ * <li> If {@link #getHtmlText} is non-null, return that.
+ * <li> If {@link #getText} is non-null, return that, converting to
+ * valid HTML text. If this text contains style spans,
+ * {@link Html#toHtml(Spanned) Html.toHtml(Spanned)} is used to
+ * convert them to HTML formatting.
+ * <li> If {@link #getUri} is non-null, try to retrieve its data
+ * as a text stream from its content provider. If the provider can
+ * supply text/html data, that will be preferred and returned as-is.
+ * Otherwise, any text/* data will be returned and escaped to HTML.
+ * If it is not a content: URI or the content provider does not supply
+ * a text representation, HTML text containing a link to the URI
+ * will be returned.
+ * <li> If {@link #getIntent} is non-null, convert that to an intent:
+ * URI and return as an HTML link.
+ * <li> Otherwise, return an empty string.
+ * </ul>
+ *
+ * @param context The caller's Context, from which its ContentResolver
+ * and other things can be retrieved.
+ * @return Returns the item's representation as HTML text.
+ */
+ public String coerceToHtmlText(Context context) {
+ // If the item has an explicit HTML value, simply return that.
+ String htmlText = getHtmlText();
+ if (htmlText != null) {
+ return htmlText;
+ }
+
+ // If this Item has a plain text value, return it as HTML.
+ CharSequence text = getText();
+ if (text != null) {
+ if (text instanceof Spanned) {
+ return Html.toHtml((Spanned)text);
+ }
+ return Html.escapeHtml(text);
+ }
+
+ text = coerceToHtmlOrStyledText(context, false);
+ return text != null ? text.toString() : null;
+ }
+
+ private CharSequence coerceToHtmlOrStyledText(Context context, boolean styled) {
+ // If this Item has a URI value, try using that.
+ if (mUri != null) {
+
+ // Check to see what data representations the content
+ // provider supports. We would like HTML text, but if that
+ // is not possible we'll live with plan text.
+ String[] types = context.getContentResolver().getStreamTypes(mUri, "text/*");
+ boolean hasHtml = false;
+ boolean hasText = false;
+ if (types != null) {
+ for (String type : types) {
+ if ("text/html".equals(type)) {
+ hasHtml = true;
+ } else if (type.startsWith("text/")) {
+ hasText = true;
+ }
+ }
+ }
+
+ // If the provider can serve data we can use, open and load it.
+ if (hasHtml || hasText) {
+ FileInputStream stream = null;
+ try {
+ // Ask for a stream of the desired type.
+ AssetFileDescriptor descr = context.getContentResolver()
+ .openTypedAssetFileDescriptor(mUri,
+ hasHtml ? "text/html" : "text/plain", null);
+ stream = descr.createInputStream();
+ InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
+
+ // Got it... copy the stream into a local string and return it.
+ StringBuilder builder = new StringBuilder(128);
+ char[] buffer = new char[8192];
+ int len;
+ while ((len=reader.read(buffer)) > 0) {
+ builder.append(buffer, 0, len);
+ }
+ String text = builder.toString();
+ if (hasHtml) {
+ if (styled) {
+ // We loaded HTML formatted text and the caller
+ // want styled text, convert it.
+ try {
+ CharSequence newText = Html.fromHtml(text);
+ return newText != null ? newText : text;
+ } catch (RuntimeException e) {
+ return text;
+ }
+ } else {
+ // We loaded HTML formatted text and that is what
+ // the caller wants, just return it.
+ return text.toString();
+ }
+ }
+ if (styled) {
+ // We loaded plain text and the caller wants styled
+ // text, that is all we have so return it.
+ return text;
+ } else {
+ // We loaded plain text and the caller wants HTML
+ // text, escape it for HTML.
+ return Html.escapeHtml(text);
+ }
+
+ } catch (FileNotFoundException e) {
+ // Unable to open content URI as text... not really an
+ // error, just something to ignore.
+
+ } catch (IOException e) {
+ // Something bad has happened.
+ Log.w("ClippedData", "Failure loading text", e);
+ return Html.escapeHtml(e.toString());
+
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ // If we couldn't open the URI as a stream, then we can build
+ // some HTML text with the URI itself.
+ // probably serves fairly well as a textual representation.
+ if (styled) {
+ return uriToStyledText(mUri.toString());
+ } else {
+ return uriToHtml(mUri.toString());
+ }
+ }
+
+ // Finally, if all we have is an Intent, then we can just turn that
+ // into text. Not the most user-friendly thing, but it's something.
+ if (mIntent != null) {
+ if (styled) {
+ return uriToStyledText(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ } else {
+ return uriToHtml(mIntent.toUri(Intent.URI_INTENT_SCHEME));
+ }
+ }
+
+ // Shouldn't get here, but just in case...
+ return "";
+ }
+
+ private String uriToHtml(String uri) {
+ StringBuilder builder = new StringBuilder(256);
+ builder.append("<a href=\"");
+ builder.append(uri);
+ builder.append("\">");
+ builder.append(Html.escapeHtml(uri));
+ builder.append("</a>");
+ return builder.toString();
+ }
+
+ private CharSequence uriToStyledText(String uri) {
+ SpannableStringBuilder builder = new SpannableStringBuilder();
+ builder.append(uri);
+ builder.setSpan(new URLSpan(uri), 0, builder.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ return builder;
+ }
+
@Override
public String toString() {
StringBuilder b = new StringBuilder(128);
@@ -335,7 +591,10 @@
/** @hide */
public void toShortString(StringBuilder b) {
- if (mText != null) {
+ if (mHtmlText != null) {
+ b.append("H:");
+ b.append(mHtmlText);
+ } else if (mText != null) {
b.append("T:");
b.append(mText);
} else if (mUri != null) {
@@ -409,6 +668,22 @@
}
/**
+ * Create a new ClipData holding data of the type
+ * {@link ClipDescription#MIMETYPE_TEXT_HTML}.
+ *
+ * @param label User-visible label for the clip data.
+ * @param text The text of clip as plain text, for receivers that don't
+ * handle HTML. This is required.
+ * @param htmlText The actual HTML text in the clip.
+ * @return Returns a new ClipData containing the specified data.
+ */
+ static public ClipData newHtmlText(CharSequence label, CharSequence text,
+ String htmlText) {
+ Item item = new Item(text, htmlText);
+ return new ClipData(label, MIMETYPES_TEXT_HTML, item);
+ }
+
+ /**
* Create a new ClipData holding an Intent with MIME type
* {@link ClipDescription#MIMETYPE_TEXT_INTENT}.
*
@@ -574,6 +849,7 @@
for (int i=0; i<N; i++) {
Item item = mItems.get(i);
TextUtils.writeToParcel(item.mText, dest, flags);
+ dest.writeString(item.mHtmlText);
if (item.mIntent != null) {
dest.writeInt(1);
item.mIntent.writeToParcel(dest, flags);
@@ -600,9 +876,10 @@
final int N = in.readInt();
for (int i=0; i<N; i++) {
CharSequence text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ String htmlText = in.readString();
Intent intent = in.readInt() != 0 ? Intent.CREATOR.createFromParcel(in) : null;
Uri uri = in.readInt() != 0 ? Uri.CREATOR.createFromParcel(in) : null;
- mItems.add(new Item(text, intent, uri));
+ mItems.add(new Item(text, htmlText, intent, uri));
}
}
diff --git a/core/java/android/content/ClipDescription.java b/core/java/android/content/ClipDescription.java
index c6b51ef..5cb6e77 100644
--- a/core/java/android/content/ClipDescription.java
+++ b/core/java/android/content/ClipDescription.java
@@ -41,6 +41,11 @@
public static final String MIMETYPE_TEXT_PLAIN = "text/plain";
/**
+ * The MIME type for a clip holding HTML text.
+ */
+ public static final String MIMETYPE_TEXT_HTML = "text/html";
+
+ /**
* The MIME type for a clip holding one or more URIs. This should be
* used for URIs that are meaningful to a user (such as an http: URI).
* It should <em>not</em> be used for a content: URI that references some
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 05ef194..1206056 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -1127,7 +1127,7 @@
/**
* Print the Provider's state into the given stream. This gets invoked if
- * you run "adb shell dumpsys activity provider <provider_component_name>".
+ * you run "adb shell dumpsys activity provider <provider_component_name>".
*
* @param prefix Desired prefix to prepend at each line of output.
* @param fd The raw file descriptor that the dump is being sent to.
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 2930998..722fdc6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -248,7 +248,7 @@
* @param mimeTypeFilter The desired MIME type. This may be a pattern,
* such as *\/*, to query for all available MIME types that match the
* pattern.
- * @return Returns an array of MIME type strings for all availablle
+ * @return Returns an array of MIME type strings for all available
* data streams that match the given mimeTypeFilter. If there are none,
* null is returned.
*/
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 18d682d..19e4372 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -954,7 +954,18 @@
* using EXTRA_TEXT, the MIME type should be "text/plain"; otherwise it
* should be the MIME type of the data in EXTRA_STREAM. Use {@literal *}/*
* if the MIME type is unknown (this will only allow senders that can
- * handle generic data streams).
+ * handle generic data streams). If using {@link #EXTRA_TEXT}, you can
+ * also optionally supply {@link #EXTRA_HTML_TEXT} for clients to retrieve
+ * your text with HTML formatting.
+ * <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
* <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
@@ -967,11 +978,13 @@
/**
* Activity Action: Deliver multiple data to someone else.
* <p>
- * Like ACTION_SEND, except the data is multiple.
+ * Like {@link #ACTION_SEND}, except the data is multiple.
* <p>
* Input: {@link #getType} is the MIME type of the data being sent.
* get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link
- * #EXTRA_STREAM} field, containing the data to be sent.
+ * #EXTRA_STREAM} field, containing the data to be sent. If using
+ * {@link #EXTRA_TEXT}, you can also optionally supply {@link #EXTRA_HTML_TEXT}
+ * for clients to retrieve your text with HTML formatting.
* <p>
* Multiple types are supported, and receivers should handle mixed types
* whenever possible. The right way for the receiver to check them is to
@@ -983,6 +996,15 @@
* be image/jpg, but if you are sending image/jpg and image/png, then the
* intent's type should be image/*.
* <p>
+ * As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN}, the data
+ * being sent can be supplied through {@link #setClipData(ClipData)}. This
+ * allows you to use {@link #FLAG_GRANT_READ_URI_PERMISSION} when sharing
+ * content: URIs and other advanced features of {@link ClipData}. If
+ * using this approach, you still must supply the same data through the
+ * {@link #EXTRA_TEXT} or {@link #EXTRA_STREAM} fields described below
+ * for compatibility with old applications. If you don't set a ClipData,
+ * it will be copied there for you when calling {@link Context#startActivity(Intent)}.
+ * <p>
* Optional standard extras, which may be interpreted by some recipients as
* appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC},
* {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}.
@@ -2501,6 +2523,14 @@
public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
/**
+ * A constant String that is associated with the Intent, used with
+ * {@link #ACTION_SEND} to supply an alternative to {@link #EXTRA_TEXT}
+ * as HTML formatted text. Note that you <em>must</em> also supply
+ * {@link #EXTRA_TEXT}.
+ */
+ public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+
+ /**
* A content: URI holding a stream of data associated with the Intent,
* used with {@link #ACTION_SEND} to supply the data being sent.
*/
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 6219de7..34c40a0 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -205,6 +205,9 @@
private final PowerManager mPowerManager;
+ // Use this as a random offset to seed all periodic syncs
+ private int mSyncRandomOffsetMillis;
+
private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
@@ -438,6 +441,9 @@
// do this synchronously to ensure we have the accounts before this call returns
onAccountsUpdated(null);
}
+
+ // Pick a random second in a day to seed all periodic syncs
+ mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
}
/**
@@ -666,6 +672,7 @@
private void sendCheckAlarmsMessage() {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+ mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
}
@@ -714,6 +721,8 @@
}
private void increaseBackoffSetting(SyncOperation op) {
+ // TODO: Use this function to align it to an already scheduled sync
+ // operation in the specified window
final long now = SystemClock.elapsedRealtime();
final Pair<Long, Long> previousSettings =
@@ -1060,6 +1069,8 @@
final long now = SystemClock.elapsedRealtime();
pw.print("now: "); pw.print(now);
pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+ pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+ pw.println(" (HH:MM:SS)");
pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
pw.println(" (HH:MM:SS)");
pw.print("time spent syncing: ");
@@ -1771,6 +1782,9 @@
AccountAndUser[] accounts = mAccounts;
final long nowAbsolute = System.currentTimeMillis();
+ final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+ ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
+
ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
for (SyncStorageEngine.AuthorityInfo info : infos) {
// skip the sync if the account of this operation no longer exists
@@ -1792,16 +1806,32 @@
SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
final Bundle extras = info.periodicSyncs.get(i).first;
- final Long periodInSeconds = info.periodicSyncs.get(i).second;
+ final Long periodInMillis = info.periodicSyncs.get(i).second * 1000;
// find when this periodic sync was last scheduled to run
final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
- // compute when this periodic sync should next run - this can be in the future
- // for example if the user changed the time, synced and changed back.
- final long nextPollTimeAbsolute = lastPollTimeAbsolute > nowAbsolute
- ? nowAbsolute
- : lastPollTimeAbsolute + periodInSeconds * 1000;
- // if it is ready to run then schedule it and mark it as having been scheduled
- if (nextPollTimeAbsolute <= nowAbsolute) {
+
+ long remainingMillis
+ = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+
+ /*
+ * Sync scheduling strategy:
+ * Set the next periodic sync based on a random offset (in seconds).
+ *
+ * Also sync right now if any of the following cases hold
+ * and mark it as having been scheduled
+ *
+ * Case 1: This sync is ready to run now.
+ * Case 2: If the lastPollTimeAbsolute is in the future,
+ * sync now and reinitialize. This can happen for
+ * example if the user changed the time, synced and
+ * changed back.
+ * Case 3: If we failed to sync at the last scheduled time
+ */
+ if (remainingMillis == periodInMillis // Case 1
+ || lastPollTimeAbsolute > nowAbsolute // Case 2
+ || (nowAbsolute - lastPollTimeAbsolute
+ >= periodInMillis)) { // Case 3
+ // Sync now
final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
info.account, info.userId, info.authority);
final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
@@ -1819,12 +1849,13 @@
info.account, info.userId, info.authority),
syncAdapterInfo.type.allowParallelSyncs()));
status.setPeriodicSyncTime(i, nowAbsolute);
- } else {
- // it isn't ready to run, remember this time if it is earlier than
- // earliestFuturePollTime
- if (nextPollTimeAbsolute < earliestFuturePollTime) {
- earliestFuturePollTime = nextPollTimeAbsolute;
- }
+ }
+ // Compute when this periodic sync should next run
+ final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+
+ // remember this time if it is earlier than earliestFuturePollTime
+ if (nextPollTimeAbsolute < earliestFuturePollTime) {
+ earliestFuturePollTime = nextPollTimeAbsolute;
}
}
}
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index d3baf70..d821918 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -37,6 +37,7 @@
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -49,6 +50,7 @@
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
+import java.util.Random;
import java.util.TimeZone;
import java.util.List;
@@ -65,6 +67,7 @@
private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+ private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
private static final String XML_ATTR_ENABLED = "enabled";
private static final String XML_ATTR_USER = "user";
private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
@@ -277,6 +280,8 @@
private static volatile SyncStorageEngine sSyncStorageEngine = null;
+ private int mSyncRandomOffset;
+
/**
* This file contains the core engine state: all accounts and the
* settings for them. It must never be lost, and should be changed
@@ -375,6 +380,10 @@
}
}
+ public int getSyncRandomOffset() {
+ return mSyncRandomOffset;
+ }
+
public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
synchronized (mAuthorities) {
mChangeListeners.register(callback, mask);
@@ -1465,6 +1474,16 @@
} catch (NumberFormatException e) {
// don't care
}
+ String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+ try {
+ mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+ } catch (NumberFormatException e) {
+ mSyncRandomOffset = 0;
+ }
+ if (mSyncRandomOffset == 0) {
+ Random random = new Random(System.currentTimeMillis());
+ mSyncRandomOffset = random.nextInt(86400);
+ }
mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
eventType = parser.next();
AuthorityInfo authority = null;
@@ -1705,6 +1724,7 @@
out.startTag(null, "accounts");
out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+ out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
// Write the Sync Automatically flags for each user
final int M = mMasterSyncAutomatically.size();
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 56fd5f8..9b8454a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -373,6 +373,6 @@
List<UserInfo> getUsers();
UserInfo getUser(int userId);
- void setPermissionEnforcement(String permission, int enforcement);
- int getPermissionEnforcement(String permission);
+ void setPermissionEnforced(String permission, boolean enforced);
+ boolean isPermissionEnforced(String permission);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06b4a5..675f77e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -28,6 +28,7 @@
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.util.AndroidException;
import android.util.DisplayMetrics;
@@ -1091,9 +1092,7 @@
= "android.content.pm.extra.VERIFICATION_INSTALL_FLAGS";
/** {@hide} */
- public static final int ENFORCEMENT_DEFAULT = 0;
- /** {@hide} */
- public static final int ENFORCEMENT_YES = 1;
+ public static final boolean DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE = !"user".equals(Build.TYPE);
/**
* Retrieve overall information about an application package that is
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2af58be..c682852 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -32,7 +32,6 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.util.LongSparseArray;
@@ -86,8 +85,8 @@
// single-threaded, and after that these are immutable.
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables
= new LongSparseArray<Drawable.ConstantState>();
- private static final SparseArray<ColorStateList> mPreloadedColorStateLists
- = new SparseArray<ColorStateList>();
+ private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
+ = new LongSparseArray<ColorStateList>();
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
= new LongSparseArray<Drawable.ConstantState>();
private static boolean mPreloaded;
@@ -98,8 +97,8 @@
// These are protected by the mTmpValue lock.
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
- private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache
- = new SparseArray<WeakReference<ColorStateList> >();
+ private final LongSparseArray<WeakReference<ColorStateList> > mColorStateListCache
+ = new LongSparseArray<WeakReference<ColorStateList> >();
private final LongSparseArray<WeakReference<Drawable.ConstantState> > mColorDrawableCache
= new LongSparseArray<WeakReference<Drawable.ConstantState> >();
private boolean mPreloading;
@@ -118,22 +117,6 @@
private CompatibilityInfo mCompatibilityInfo;
- private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>(0) {
- @Override
- public void put(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- @Override
- public void append(long k, Object o) {
- throw new UnsupportedOperationException();
- }
- };
-
- @SuppressWarnings("unchecked")
- private static <T> LongSparseArray<T> emptySparseArray() {
- return (LongSparseArray<T>) EMPTY_ARRAY;
- }
-
/** @hide */
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
@@ -180,9 +163,8 @@
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
- public Resources(AssetManager assets, DisplayMetrics metrics,
- Configuration config) {
- this(assets, metrics, config, (CompatibilityInfo) null);
+ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
+ this(assets, metrics, config, null);
}
/**
@@ -1883,7 +1865,8 @@
return dr;
}
- Drawable.ConstantState cs = isColorDrawable ? sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
+ Drawable.ConstantState cs = isColorDrawable ?
+ sPreloadedColorDrawables.get(key) : sPreloadedDrawables.get(key);
if (cs != null) {
dr = cs.newDrawable(this);
} else {
@@ -2005,21 +1988,21 @@
}
}
- final int key = (value.assetCookie << 24) | value.data;
+ final long key = (((long) value.assetCookie) << 32) | value.data;
ColorStateList csl;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
csl = ColorStateList.valueOf(value.data);
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
}
return csl;
@@ -2030,7 +2013,7 @@
return csl;
}
- csl = mPreloadedColorStateLists.get(key);
+ csl = sPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
@@ -2063,14 +2046,13 @@
if (csl != null) {
if (mPreloading) {
- mPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl);
} else {
synchronized (mTmpValue) {
//Log.i(TAG, "Saving cached color state list @ #" +
// Integer.toHexString(key.intValue())
// + " in " + this + ": " + csl);
- mColorStateListCache.put(
- key, new WeakReference<ColorStateList>(csl));
+ mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
}
}
}
@@ -2078,7 +2060,7 @@
return csl;
}
- private ColorStateList getCachedColorStateList(int key) {
+ private ColorStateList getCachedColorStateList(long key) {
synchronized (mTmpValue) {
WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
if (wr != null) { // we have the key
@@ -2088,8 +2070,7 @@
// Integer.toHexString(((Integer)key).intValue())
// + " in " + this + ": " + entry);
return entry;
- }
- else { // our entry has been purged
+ } else { // our entry has been purged
mColorStateListCache.delete(key);
}
}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 83b6986..640b47b 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -950,11 +950,10 @@
/**
* Callback interface used to notify on auto focus start and stop.
*
- * <p>This is useful for continuous autofocus -- {@link Parameters#FOCUS_MODE_CONTINUOUS_VIDEO}
- * and {@link Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can
- * show autofocus animation.</p>
- *
- * @hide
+ * <p>This is only supported in continuous autofocus modes -- {@link
+ * Parameters#FOCUS_MODE_CONTINUOUS_VIDEO} and {@link
+ * Parameters#FOCUS_MODE_CONTINUOUS_PICTURE}. Applications can show
+ * autofocus animation based on this.</p>
*/
public interface AutoFocusMoveCallback
{
@@ -962,7 +961,7 @@
* Called when the camera auto focus starts or stops.
*
* @param start true if focus starts to move, false if focus stops to move
- * @param camera the Camera service object
+ * @param camera the Camera service object
*/
void onAutoFocusMoving(boolean start, Camera camera);
}
@@ -971,7 +970,6 @@
* Sets camera auto-focus move callback.
*
* @param cb the callback to run
- * @hide
*/
public void setAutoFocusMoveCallback(AutoFocusMoveCallback cb) {
mAutoFocusMoveCallback = cb;
diff --git a/core/java/android/hardware/input/IInputDevicesChangedListener.aidl b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
new file mode 100644
index 0000000..5d8ada1
--- /dev/null
+++ b/core/java/android/hardware/input/IInputDevicesChangedListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ * 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.hardware.input;
+
+/** @hide */
+interface IInputDevicesChangedListener {
+ /* Called when input devices changed, such as a device being added,
+ * removed or changing configuration.
+ *
+ * The parameter is an array of pairs (deviceId, generation) indicating the current
+ * device id and generation of all input devices. The client can determine what
+ * has happened by comparing the result to its prior observations.
+ */
+ oneway void onInputDevicesChanged(in int[] deviceIdAndGeneration);
+}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 47e0d1e..3137947 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -17,6 +17,8 @@
package android.hardware.input;
import android.hardware.input.KeyboardLayout;
+import android.hardware.input.IInputDevicesChangedListener;
+import android.os.IBinder;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -42,4 +44,11 @@
String getKeyboardLayoutForInputDevice(String inputDeviceDescriptor);
void setKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
String keyboardLayoutDescriptor);
+
+ // Registers an input devices changed listener.
+ void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
+
+ // Input device vibrator control.
+ void vibrate(int deviceId, in long[] pattern, int repeat, IBinder token);
+ void cancelVibrate(int deviceId, IBinder token);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 3b3c237..b39b823 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,9 +19,14 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
@@ -29,6 +34,8 @@
import android.view.InputDevice;
import android.view.InputEvent;
+import java.util.ArrayList;
+
/**
* Provides information about input devices and available key layouts.
* <p>
@@ -40,11 +47,22 @@
*/
public final class InputManager {
private static final String TAG = "InputManager";
+ private static final boolean DEBUG = false;
+
+ private static final int MSG_DEVICE_ADDED = 1;
+ private static final int MSG_DEVICE_REMOVED = 2;
+ private static final int MSG_DEVICE_CHANGED = 3;
private static InputManager sInstance;
private final IInputManager mIm;
- private final SparseArray<InputDevice> mInputDevices = new SparseArray<InputDevice>();
+
+ // Guarded by mInputDevicesLock
+ private final Object mInputDevicesLock = new Object();
+ private SparseArray<InputDevice> mInputDevices;
+ private InputDevicesChangedListener mInputDevicesChangedListener;
+ private final ArrayList<InputDeviceListenerDelegate> mInputDeviceListeners =
+ new ArrayList<InputDeviceListenerDelegate>();
/**
* Broadcast Action: Query available keyboard layouts.
@@ -169,6 +187,103 @@
}
/**
+ * Gets information about the input device with the specified id.
+ * @param id The device id.
+ * @return The input device or null if not found.
+ */
+ public InputDevice getInputDevice(int id) {
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ int index = mInputDevices.indexOfKey(id);
+ if (index < 0) {
+ return null;
+ }
+
+ InputDevice inputDevice = mInputDevices.valueAt(index);
+ if (inputDevice == null) {
+ try {
+ inputDevice = mIm.getInputDevice(id);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Could not get input device information.", ex);
+ }
+ }
+ mInputDevices.setValueAt(index, inputDevice);
+ return inputDevice;
+ }
+ }
+
+ /**
+ * Gets the ids of all input devices in the system.
+ * @return The input device ids.
+ */
+ public int[] getInputDeviceIds() {
+ synchronized (mInputDevicesLock) {
+ populateInputDevicesLocked();
+
+ final int count = mInputDevices.size();
+ final int[] ids = new int[count];
+ for (int i = 0; i < count; i++) {
+ ids[i] = mInputDevices.keyAt(i);
+ }
+ return ids;
+ }
+ }
+
+ /**
+ * Registers an input device listener to receive notifications about when
+ * input devices are added, removed or changed.
+ *
+ * @param listener The listener to register.
+ * @param handler The handler on which the listener should be invoked, or null
+ * if the listener should be invoked on the calling thread's looper.
+ *
+ * @see #unregisterInputDeviceListener
+ */
+ public void registerInputDeviceListener(InputDeviceListener listener, Handler handler) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDevicesLock) {
+ int index = findInputDeviceListenerLocked(listener);
+ if (index < 0) {
+ mInputDeviceListeners.add(new InputDeviceListenerDelegate(listener, handler));
+ }
+ }
+ }
+
+ /**
+ * Unregisters an input device listener.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @see #registerInputDeviceListener
+ */
+ public void unregisterInputDeviceListener(InputDeviceListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDevicesLock) {
+ int index = findInputDeviceListenerLocked(listener);
+ if (index >= 0) {
+ mInputDeviceListeners.remove(index);
+ }
+ }
+ }
+
+ private int findInputDeviceListenerLocked(InputDeviceListener listener) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ if (mInputDeviceListeners.get(i).mListener == listener) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
* Gets information about all supported keyboard layouts.
* <p>
* The input manager consults the built-in keyboard layouts as well
@@ -327,50 +442,6 @@
}
/**
- * Gets information about the input device with the specified id.
- * @param id The device id.
- * @return The input device or null if not found.
- *
- * @hide
- */
- public InputDevice getInputDevice(int id) {
- synchronized (mInputDevices) {
- InputDevice inputDevice = mInputDevices.get(id);
- if (inputDevice != null) {
- return inputDevice;
- }
- }
- final InputDevice newInputDevice;
- try {
- newInputDevice = mIm.getInputDevice(id);
- } catch (RemoteException ex) {
- throw new RuntimeException("Could not get input device information.", ex);
- }
- synchronized (mInputDevices) {
- InputDevice inputDevice = mInputDevices.get(id);
- if (inputDevice != null) {
- return inputDevice;
- }
- mInputDevices.put(id, newInputDevice);
- return newInputDevice;
- }
- }
-
- /**
- * Gets the ids of all input devices in the system.
- * @return The input device ids.
- *
- * @hide
- */
- public int[] getInputDeviceIds() {
- try {
- return mIm.getInputDeviceIds();
- } catch (RemoteException ex) {
- throw new RuntimeException("Could not get input device ids.", ex);
- }
- }
-
- /**
* Queries the framework about whether any physical keys exist on the
* any keyboard attached to the device that are capable of producing the given
* array of key codes.
@@ -429,4 +500,201 @@
return false;
}
}
+
+ private void populateInputDevicesLocked() {
+ if (mInputDevicesChangedListener == null) {
+ final InputDevicesChangedListener listener = new InputDevicesChangedListener();
+ try {
+ mIm.registerInputDevicesChangedListener(listener);
+ } catch (RemoteException ex) {
+ throw new RuntimeException(
+ "Could not get register input device changed listener", ex);
+ }
+ mInputDevicesChangedListener = listener;
+ }
+
+ if (mInputDevices == null) {
+ final int[] ids;
+ try {
+ ids = mIm.getInputDeviceIds();
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Could not get input device ids.", ex);
+ }
+
+ mInputDevices = new SparseArray<InputDevice>();
+ for (int i = 0; i < ids.length; i++) {
+ mInputDevices.put(ids[i], null);
+ }
+ }
+ }
+
+ private void onInputDevicesChanged(int[] deviceIdAndGeneration) {
+ if (DEBUG) {
+ Log.d(TAG, "Received input devices changed.");
+ }
+
+ synchronized (mInputDevicesLock) {
+ for (int i = mInputDevices.size(); --i > 0; ) {
+ final int deviceId = mInputDevices.keyAt(i);
+ if (!containsDeviceId(deviceIdAndGeneration, deviceId)) {
+ if (DEBUG) {
+ Log.d(TAG, "Device removed: " + deviceId);
+ }
+ mInputDevices.removeAt(i);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_REMOVED, deviceId);
+ }
+ }
+
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ final int deviceId = deviceIdAndGeneration[i];
+ int index = mInputDevices.indexOfKey(deviceId);
+ if (index >= 0) {
+ final InputDevice device = mInputDevices.valueAt(index);
+ if (device != null) {
+ final int generation = deviceIdAndGeneration[i + 1];
+ if (device.getGeneration() != generation) {
+ if (DEBUG) {
+ Log.d(TAG, "Device changed: " + deviceId);
+ }
+ mInputDevices.setValueAt(index, null);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_CHANGED, deviceId);
+ }
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Device added: " + deviceId);
+ }
+ mInputDevices.put(deviceId, null);
+ sendMessageToInputDeviceListenersLocked(MSG_DEVICE_ADDED, deviceId);
+ }
+ }
+ }
+ }
+
+ private void sendMessageToInputDeviceListenersLocked(int what, int deviceId) {
+ final int numListeners = mInputDeviceListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ InputDeviceListenerDelegate listener = mInputDeviceListeners.get(i);
+ listener.sendMessage(listener.obtainMessage(what, deviceId, 0));
+ }
+ }
+
+ private static boolean containsDeviceId(int[] deviceIdAndGeneration, int deviceId) {
+ for (int i = 0; i < deviceIdAndGeneration.length; i += 2) {
+ if (deviceIdAndGeneration[i] == deviceId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets a vibrator service associated with an input device, assuming it has one.
+ * @return The vibrator, never null.
+ * @hide
+ */
+ public Vibrator getInputDeviceVibrator(int deviceId) {
+ return new InputDeviceVibrator(deviceId);
+ }
+
+ /**
+ * Listens for changes in input devices.
+ */
+ public interface InputDeviceListener {
+ /**
+ * Called whenever an input device has been added to the system.
+ * Use {@link InputManager#getInputDevice} to get more information about the device.
+ *
+ * @param deviceId The id of the input device that was added.
+ */
+ void onInputDeviceAdded(int deviceId);
+
+ /**
+ * Called whenever an input device has been removed from the system.
+ *
+ * @param deviceId The id of the input device that was removed.
+ */
+ void onInputDeviceRemoved(int deviceId);
+
+ /**
+ * Called whenever the properties of an input device have changed since they
+ * were last queried. Use {@link InputManager#getInputDevice} to get
+ * a fresh {@link InputDevice} object with the new properties.
+ *
+ * @param deviceId The id of the input device that changed.
+ */
+ void onInputDeviceChanged(int deviceId);
+ }
+
+ private final class InputDevicesChangedListener extends IInputDevicesChangedListener.Stub {
+ @Override
+ public void onInputDevicesChanged(int[] deviceIdAndGeneration) throws RemoteException {
+ InputManager.this.onInputDevicesChanged(deviceIdAndGeneration);
+ }
+ }
+
+ private static final class InputDeviceListenerDelegate extends Handler {
+ public final InputDeviceListener mListener;
+
+ public InputDeviceListenerDelegate(InputDeviceListener listener, Handler handler) {
+ super(handler != null ? handler.getLooper() : Looper.myLooper());
+ mListener = listener;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DEVICE_ADDED:
+ mListener.onInputDeviceAdded(msg.arg1);
+ break;
+ case MSG_DEVICE_REMOVED:
+ mListener.onInputDeviceRemoved(msg.arg1);
+ break;
+ case MSG_DEVICE_CHANGED:
+ mListener.onInputDeviceChanged(msg.arg1);
+ break;
+ }
+ }
+ }
+
+ private final class InputDeviceVibrator extends Vibrator {
+ private final int mDeviceId;
+ private final Binder mToken;
+
+ public InputDeviceVibrator(int deviceId) {
+ mDeviceId = deviceId;
+ mToken = new Binder();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ vibrate(new long[] { 0, milliseconds}, -1);
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ try {
+ mIm.vibrate(mDeviceId, pattern, repeat, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to vibrate.", ex);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ try {
+ mIm.cancelVibrate(mDeviceId, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to cancel vibration.", ex);
+ }
+ }
+ }
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ba7dc4a3..332f40a 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1712,8 +1712,8 @@
/**
* Override this to intercept key down events before they are processed by the
- * application. If you return true, the application will not itself
- * process the event. If you return true, the normal application processing
+ * application. If you return true, the application will not
+ * process the event itself. If you return false, the normal application processing
* will occur as if the IME had not seen the event at all.
*
* <p>The default implementation intercepts {@link KeyEvent#KEYCODE_BACK
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index de16985..ef4209f 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -373,10 +373,11 @@
}
/**
- * Gets you info about the current data network.
- * Call {@link NetworkInfo#isConnected()} on the returned {@link NetworkInfo}
- * to check if the device has a data connection.
- */
+ * Returns details about the currently active data network. When connected,
+ * this network is the default route for outgoing connections. You should
+ * always check {@link NetworkInfo#isConnected()} before initiating network
+ * traffic. This may return {@code null} when no networks are available.
+ */
public NetworkInfo getActiveNetworkInfo() {
try {
return mService.getActiveNetworkInfo();
@@ -856,4 +857,19 @@
} catch (RemoteException e) {}
return false;
}
+
+ /**
+ * Returns if the currently active data network is metered. A network is
+ * classified as metered when the user is sensitive to heavy data usage on
+ * that connection. You should check this before doing large data transfers,
+ * and warn the user or delay the operation until another network is
+ * available.
+ */
+ public boolean isActiveNetworkMetered() {
+ try {
+ return mService.isActiveNetworkMetered();
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 7046008..92aeff2 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -51,6 +51,7 @@
NetworkState[] getAllNetworkState();
NetworkQuotaInfo getActiveNetworkQuotaInfo();
+ boolean isActiveNetworkMetered();
boolean setRadios(boolean onOff);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 89c9c36..3250ae7 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -32,6 +32,7 @@
/** Control UID policies. */
void setAppPolicy(int appId, int policy);
int getAppPolicy(int appId);
+ int[] getAppsWithPolicy(int policy);
boolean isUidForeground(int uid);
@@ -50,5 +51,6 @@
boolean getRestrictBackground();
NetworkQuotaInfo getNetworkQuotaInfo(in NetworkState state);
+ boolean isNetworkMetered(in NetworkState state);
}
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index b4f6367..08d4c6c 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -32,6 +32,9 @@
/** Return data layer snapshot of UID network usage. */
NetworkStats getDataLayerSnapshotForUid(int uid);
+ /** Return set of any ifaces associated with mobile networks since boot. */
+ String[] getMobileIfaces();
+
/** Increment data layer count of operations performed for UID and tag. */
void incrementOperationCount(int uid, int tag, int operationCount);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 2b36131..07bfd4b 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -92,6 +92,14 @@
}
}
+ public int[] getAppsWithPolicy(int policy) {
+ try {
+ return mService.getAppsWithPolicy(policy);
+ } catch (RemoteException e) {
+ return new int[0];
+ }
+ }
+
public void registerListener(INetworkPolicyListener listener) {
try {
mService.registerListener(listener);
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 50432a1..39a4d7b 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -48,6 +48,8 @@
public static final int MATCH_MOBILE_4G = 3;
public static final int MATCH_WIFI = 4;
public static final int MATCH_ETHERNET = 5;
+ public static final int MATCH_MOBILE_WILDCARD = 6;
+ public static final int MATCH_WIFI_WILDCARD = 7;
/**
* Set of {@link NetworkInfo#getType()} that reflect data usage.
@@ -86,11 +88,19 @@
}
/**
+ * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks,
+ * regardless of IMSI.
+ */
+ public static NetworkTemplate buildTemplateMobileWildcard() {
+ return new NetworkTemplate(MATCH_MOBILE_WILDCARD, null, null);
+ }
+
+ /**
* Template to match all {@link ConnectivityManager#TYPE_WIFI} networks,
* regardless of SSID.
*/
public static NetworkTemplate buildTemplateWifiWildcard() {
- return new NetworkTemplate(MATCH_WIFI, null, null);
+ return new NetworkTemplate(MATCH_WIFI_WILDCARD, null, null);
}
@Deprecated
@@ -198,6 +208,10 @@
return matchesWifi(ident);
case MATCH_ETHERNET:
return matchesEthernet(ident);
+ case MATCH_MOBILE_WILDCARD:
+ return matchesMobileWildcard(ident);
+ case MATCH_WIFI_WILDCARD:
+ return matchesWifiWildcard(ident);
default:
throw new IllegalArgumentException("unknown network template");
}
@@ -257,13 +271,7 @@
private boolean matchesWifi(NetworkIdentity ident) {
switch (ident.mType) {
case TYPE_WIFI:
- if (mNetworkId == null) {
- return true;
- } else {
- return Objects.equal(mNetworkId, ident.mNetworkId);
- }
- case TYPE_WIFI_P2P:
- return mNetworkId == null;
+ return Objects.equal(mNetworkId, ident.mNetworkId);
default:
return false;
}
@@ -279,6 +287,24 @@
return false;
}
+ private boolean matchesMobileWildcard(NetworkIdentity ident) {
+ if (ident.mType == TYPE_WIMAX) {
+ return true;
+ } else {
+ return contains(DATA_USAGE_NETWORK_TYPES, ident.mType);
+ }
+ }
+
+ private boolean matchesWifiWildcard(NetworkIdentity ident) {
+ switch (ident.mType) {
+ case TYPE_WIFI:
+ case TYPE_WIFI_P2P:
+ return true;
+ default:
+ return false;
+ }
+ }
+
private static String getMatchRuleName(int matchRule) {
switch (matchRule) {
case MATCH_MOBILE_3G_LOWER:
@@ -291,6 +317,10 @@
return "WIFI";
case MATCH_ETHERNET:
return "ETHERNET";
+ case MATCH_MOBILE_WILDCARD:
+ return "MOBILE_WILDCARD";
+ case MATCH_WIFI_WILDCARD:
+ return "WIFI_WILDCARD";
default:
return "UNKNOWN";
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index ee3e165..e437d2e 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -88,6 +88,16 @@
*/
public static final int TAG_SYSTEM_BACKUP = 0xFFFFFF03;
+ private static INetworkStatsService sStatsService;
+
+ private synchronized static INetworkStatsService getStatsService() {
+ if (sStatsService == null) {
+ sStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ }
+ return sStatsService;
+ }
+
/**
* Snapshot of {@link NetworkStats} when the currently active profiling
* session started, or {@code null} if no session active.
@@ -228,11 +238,9 @@
* @param operationCount Number of operations to increment count by.
*/
public static void incrementOperationCount(int tag, int operationCount) {
- final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
- statsService.incrementOperationCount(uid, tag, operationCount);
+ getStatsService().incrementOperationCount(uid, tag, operationCount);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
@@ -257,7 +265,13 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileTxPackets();
+ public static long getMobileTxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getTxPackets(iface);
+ }
+ return total;
+ }
/**
* Get the total number of packets received through the mobile interface.
@@ -265,7 +279,13 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileRxPackets();
+ public static long getMobileRxPackets() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getRxPackets(iface);
+ }
+ return total;
+ }
/**
* Get the total number of bytes transmitted through the mobile interface.
@@ -273,7 +293,13 @@
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileTxBytes();
+ public static long getMobileTxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getTxBytes(iface);
+ }
+ return total;
+ }
/**
* Get the total number of bytes received through the mobile interface.
@@ -281,7 +307,13 @@
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getMobileRxBytes();
+ public static long getMobileRxBytes() {
+ long total = 0;
+ for (String iface : getMobileIfaces()) {
+ total += getRxBytes(iface);
+ }
+ return total;
+ }
/**
* Get the total number of packets transmitted through the specified interface.
@@ -290,7 +322,9 @@
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getTxPackets(String iface);
+ public static long getTxPackets(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_TX_PACKETS);
+ }
/**
* Get the total number of packets received through the specified interface.
@@ -299,7 +333,9 @@
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getRxPackets(String iface);
+ public static long getRxPackets(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_RX_PACKETS);
+ }
/**
* Get the total number of bytes transmitted through the specified interface.
@@ -308,7 +344,9 @@
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getTxBytes(String iface);
+ public static long getTxBytes(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_TX_BYTES);
+ }
/**
* Get the total number of bytes received through the specified interface.
@@ -317,8 +355,9 @@
* {@link #UNSUPPORTED} will be returned.
* @hide
*/
- public static native long getRxBytes(String iface);
-
+ public static long getRxBytes(String iface) {
+ return nativeGetIfaceStat(iface, TYPE_RX_BYTES);
+ }
/**
* Get the total number of packets sent through all network interfaces.
@@ -326,7 +365,9 @@
* @return the number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalTxPackets();
+ public static long getTotalTxPackets() {
+ return nativeGetTotalStat(TYPE_TX_PACKETS);
+ }
/**
* Get the total number of packets received through all network interfaces.
@@ -334,7 +375,9 @@
* @return number of packets. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalRxPackets();
+ public static long getTotalRxPackets() {
+ return nativeGetTotalStat(TYPE_RX_PACKETS);
+ }
/**
* Get the total number of bytes sent through all network interfaces.
@@ -342,7 +385,9 @@
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalTxBytes();
+ public static long getTotalTxBytes() {
+ return nativeGetTotalStat(TYPE_TX_BYTES);
+ }
/**
* Get the total number of bytes received through all network interfaces.
@@ -350,7 +395,9 @@
* @return number of bytes. If the statistics are not supported by this device,
* {@link #UNSUPPORTED} will be returned.
*/
- public static native long getTotalRxBytes();
+ public static long getTotalRxBytes() {
+ return nativeGetTotalStat(TYPE_RX_BYTES);
+ }
/**
* Get the number of bytes sent through the network for this UID.
@@ -483,7 +530,6 @@
*/
public static native long getUidTcpRxSegments(int uid);
-
/**
* Get the number of UDP packets sent for this UID.
* Includes DNS requests.
@@ -515,13 +561,33 @@
* special permission.
*/
private static NetworkStats getDataLayerSnapshotForUid(Context context) {
- final INetworkStatsService statsService = INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
final int uid = android.os.Process.myUid();
try {
- return statsService.getDataLayerSnapshotForUid(uid);
+ return getStatsService().getDataLayerSnapshotForUid(uid);
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
+
+ /**
+ * Return set of any ifaces associated with mobile networks since boot.
+ * Interfaces are never removed from this list, so counters should always be
+ * monotonic.
+ */
+ private static String[] getMobileIfaces() {
+ try {
+ return getStatsService().getMobileIfaces();
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // NOTE: keep these in sync with android_net_TrafficStats.cpp
+ private static final int TYPE_RX_BYTES = 0;
+ private static final int TYPE_RX_PACKETS = 1;
+ private static final int TYPE_TX_BYTES = 2;
+ private static final int TYPE_TX_PACKETS = 3;
+
+ private static native long nativeGetTotalStat(int type);
+ private static native long nativeGetIfaceStat(String iface, int type);
}
diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java
index 47d6ec6..33c3eb9 100644
--- a/core/java/android/net/nsd/DnsSdServiceInfo.java
+++ b/core/java/android/net/nsd/DnsSdServiceInfo.java
@@ -19,6 +19,8 @@
import android.os.Parcelable;
import android.os.Parcel;
+import java.net.InetAddress;
+
/**
* Defines a service based on DNS service discovery
* {@hide}
@@ -27,20 +29,20 @@
private String mServiceName;
- private String mRegistrationType;
+ private String mServiceType;
private DnsSdTxtRecord mTxtRecord;
- private String mHostname;
+ private InetAddress mHost;
private int mPort;
- DnsSdServiceInfo() {
+ public DnsSdServiceInfo() {
}
- DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
+ public DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
mServiceName = sn;
- mRegistrationType = rt;
+ mServiceType = rt;
mTxtRecord = tr;
}
@@ -59,13 +61,13 @@
@Override
/** @hide */
public String getServiceType() {
- return mRegistrationType;
+ return mServiceType;
}
@Override
/** @hide */
public void setServiceType(String s) {
- mRegistrationType = s;
+ mServiceType = s;
}
public DnsSdTxtRecord getTxtRecord() {
@@ -76,12 +78,12 @@
mTxtRecord = new DnsSdTxtRecord(t);
}
- public String getHostName() {
- return mHostname;
+ public InetAddress getHost() {
+ return mHost;
}
- public void setHostName(String s) {
- mHostname = s;
+ public void setHost(InetAddress s) {
+ mHost = s;
}
public int getPort() {
@@ -96,7 +98,9 @@
StringBuffer sb = new StringBuffer();
sb.append("name: ").append(mServiceName).
- append("type: ").append(mRegistrationType).
+ append("type: ").append(mServiceType).
+ append("host: ").append(mHost).
+ append("port: ").append(mPort).
append("txtRecord: ").append(mTxtRecord);
return sb.toString();
}
@@ -109,9 +113,14 @@
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mServiceName);
- dest.writeString(mRegistrationType);
+ dest.writeString(mServiceType);
dest.writeParcelable(mTxtRecord, flags);
- dest.writeString(mHostname);
+ if (mHost != null) {
+ dest.writeByte((byte)1);
+ dest.writeByteArray(mHost.getAddress());
+ } else {
+ dest.writeByte((byte)0);
+ }
dest.writeInt(mPort);
}
@@ -121,9 +130,15 @@
public DnsSdServiceInfo createFromParcel(Parcel in) {
DnsSdServiceInfo info = new DnsSdServiceInfo();
info.mServiceName = in.readString();
- info.mRegistrationType = in.readString();
+ info.mServiceType = in.readString();
info.mTxtRecord = in.readParcelable(null);
- info.mHostname = in.readString();
+
+ if (in.readByte() == 1) {
+ try {
+ info.mHost = InetAddress.getByAddress(in.createByteArray());
+ } catch (java.net.UnknownHostException e) {}
+ }
+
info.mPort = in.readInt();
return info;
}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
index 6d4342c..952e02f 100644
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ b/core/java/android/net/nsd/DnsSdTxtRecord.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.os.Parcel;
+import java.util.Arrays;
+
/**
* This class handles TXT record data for DNS based service discovery as specified at
* http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
@@ -160,7 +162,7 @@
/* Gets the raw data in bytes */
public byte[] getRawData() {
- return mData;
+ return (byte[]) mData.clone();
}
private void insert(byte[] keyBytes, byte[] value, int index) {
@@ -279,6 +281,24 @@
return result != null ? result : "";
}
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof DnsSdTxtRecord)) {
+ return false;
+ }
+
+ DnsSdTxtRecord record = (DnsSdTxtRecord)o;
+ return Arrays.equals(record.mData, mData);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(mData);
+ }
+
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java
index a109a98..505f11b 100644
--- a/core/java/android/net/nsd/NsdManager.java
+++ b/core/java/android/net/nsd/NsdManager.java
@@ -93,6 +93,15 @@
/** @hide */
public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17;
+ /** @hide */
+ public static final int STOP_RESOLVE = BASE + 18;
+ /** @hide */
+ public static final int STOP_RESOLVE_FAILED = BASE + 19;
+ /** @hide */
+ public static final int STOP_RESOLVE_SUCCEEDED = BASE + 20;
+
+
+
/**
* Create a new Nsd instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -117,10 +126,23 @@
/**
* Indicates that the operation failed because the framework is busy and
- * unable to service the request
+ * unable to service the request.
*/
public static final int BUSY = 2;
+ /**
+ * Indicates that the operation failed because it is already active.
+ */
+ public static final int ALREADY_ACTIVE = 3;
+
+ /**
+ * Indicates that the operation failed because maximum limit on
+ * service registrations has reached.
+ */
+ public static final int MAX_REGS_REACHED = 4;
+
+
+
/** Interface for callback invocation when framework channel is connected or lost */
public interface ChannelListener {
public void onChannelConnected(Channel c);
@@ -188,6 +210,7 @@
private DnsSdRegisterListener mDnsSdRegisterListener;
private DnsSdUpdateRegistrationListener mDnsSdUpdateListener;
private DnsSdResolveListener mDnsSdResolveListener;
+ private ActionListener mDnsSdStopResolveListener;
AsyncChannel mAsyncChannel;
ServiceHandler mHandler;
@@ -278,6 +301,16 @@
(DnsSdServiceInfo) message.obj);
}
break;
+ case STOP_RESOLVE_FAILED:
+ if (mDnsSdStopResolveListener!= null) {
+ mDnsSdStopResolveListener.onFailure(message.arg1);
+ }
+ break;
+ case STOP_RESOLVE_SUCCEEDED:
+ if (mDnsSdStopResolveListener != null) {
+ mDnsSdStopResolveListener.onSuccess();
+ }
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -345,6 +378,14 @@
c.mDnsSdResolveListener = b;
}
+ /**
+ * Set the listener for stopping service resolution. Can be null.
+ */
+ public void setStopResolveListener(Channel c, ActionListener b) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ c.mDnsSdStopResolveListener = b;
+ }
+
public void registerService(Channel c, DnsSdServiceInfo serviceInfo) {
if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo");
@@ -378,6 +419,13 @@
c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo);
}
+ public void stopServiceResolve(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ if (c.mDnsSdResolveListener == null) throw new
+ IllegalStateException("Resolve listener needs to be set first");
+ c.mAsyncChannel.sendMessage(STOP_RESOLVE);
+ }
+
/**
* Get a reference to NetworkService handler. This is used to establish
* an AsyncChannel communication with the service
diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl
index 4e79822..1c6d5d0 100644
--- a/core/java/android/nfc/INdefPushCallback.aidl
+++ b/core/java/android/nfc/INdefPushCallback.aidl
@@ -25,7 +25,6 @@
interface INdefPushCallback
{
NdefMessage createMessage();
- Uri getUri();
- String getMimeType();
+ Uri[] getUris();
void onNdefPushComplete();
}
diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java
index f80dae4..7ffa575 100644
--- a/core/java/android/nfc/NfcActivityManager.java
+++ b/core/java/android/nfc/NfcActivityManager.java
@@ -108,8 +108,8 @@
NdefMessage ndefMessage = null; // static NDEF message
NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null;
NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null;
- Uri uri = null;
- String mimeType = null;
+ NfcAdapter.CreateBeamUrisCallback uriCallback = null;
+ Uri[] uris = null;
public NfcActivityState(Activity activity) {
if (activity.getWindow().isDestroyed()) {
throw new IllegalStateException("activity is already destroyed");
@@ -128,14 +128,19 @@
ndefMessage = null;
ndefMessageCallback = null;
onNdefPushCompleteCallback = null;
- uri = null;
- mimeType = null;
+ uriCallback = null;
+ uris = null;
}
@Override
public String toString() {
StringBuilder s = new StringBuilder("[").append(" ");
s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" ");
- s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
+ s.append(uriCallback).append(" ");
+ if (uris != null) {
+ for (Uri uri : uris) {
+ s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]");
+ }
+ }
return s.toString();
}
}
@@ -184,12 +189,25 @@
mDefaultEvent = new NfcEvent(mAdapter);
}
- public void setNdefPushContentUri(Activity activity, String mimeType, Uri uri) {
+ public void setNdefPushContentUri(Activity activity, Uri[] uris) {
boolean isResumed;
synchronized (NfcActivityManager.this) {
NfcActivityState state = getActivityState(activity);
- state.uri = uri;
- state.mimeType = mimeType;
+ state.uris = uris;
+ isResumed = state.resumed;
+ }
+ if (isResumed) {
+ requestNfcServiceCallback(true);
+ }
+ }
+
+
+ public void setNdefPushContentUriCallback(Activity activity,
+ NfcAdapter.CreateBeamUrisCallback callback) {
+ boolean isResumed;
+ synchronized (NfcActivityManager.this) {
+ NfcActivityState state = getActivityState(activity);
+ state.uriCallback = callback;
isResumed = state.resumed;
}
if (isResumed) {
@@ -271,24 +289,22 @@
/** Callback from NFC service, usually on binder thread */
@Override
- public Uri getUri() {
+ public Uri[] getUris() {
+ Uri[] uris;
+ NfcAdapter.CreateBeamUrisCallback callback;
synchronized (NfcActivityManager.this) {
NfcActivityState state = findResumedActivityState();
if (state == null) return null;
-
- return state.uri;
+ uris = state.uris;
+ callback = state.uriCallback;
+ }
+ if (callback != null) {
+ return callback.createBeamUris(mDefaultEvent);
+ } else {
+ return uris;
}
}
- /** Callback from NFC service, usually on binder thread */
- @Override
- public String getMimeType() {
- synchronized (NfcActivityManager.this) {
- NfcActivityState state = findResumedActivityState();
- if (state == null) return null;
- return state.mimeType;
- }
- }
/** Callback from NFC service, usually on binder thread */
@Override
public void onNdefPushComplete() {
@@ -358,4 +374,5 @@
}
}
}
+
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index 917751c..90f5bef 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -203,6 +203,27 @@
/** @hide */
public static final int STATE_TURNING_OFF = 4;
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_STARTED =
+ "android.nfc.action.HANDOVER_TRANSFER_STARTED";
+
+ /** @hide */
+ public static final String ACTION_HANDOVER_TRANSFER_DONE =
+ "android.nfc.action.HANDOVER_TRANSFER_DONE";
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_STATUS =
+ "android.nfc.extra.HANDOVER_TRANSFER_STATUS";
+
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0;
+ /** @hide */
+ public static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1;
+
+ /** @hide */
+ public static final String EXTRA_HANDOVER_TRANSFER_URI =
+ "android.nfc.extra.HANDOVER_TRANSFER_URI";
+
// Guarded by NfcAdapter.class
static boolean sIsInitialized = false;
@@ -281,6 +302,12 @@
public NdefMessage createNdefMessage(NfcEvent event);
}
+
+ // TODO javadoc
+ public interface CreateBeamUrisCallback {
+ public Uri[] createBeamUris(NfcEvent event);
+ }
+
/**
* Helper to check if this device has FEATURE_NFC, but without using
* a context.
@@ -556,16 +583,22 @@
}
}
- //TODO: Consider a callback alternative
- //TOOD: See if we get rid of mimeType
//TODO: make sure NFC service has permission for URI
+ //TODO: see if we will eventually support multiple URIs
//TODO: javadoc
- /** @hide */
- public void setBeamPushUri(String mimeType, Uri uri, Activity activity) {
+ public void setBeamPushUris(Uri[] uris, Activity activity) {
if (activity == null) {
throw new NullPointerException("activity cannot be null");
}
- mNfcActivityManager.setNdefPushContentUri(activity, mimeType, uri);
+ mNfcActivityManager.setNdefPushContentUri(activity, uris);
+ }
+
+ // TODO javadoc
+ public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
+ if (activity == null) {
+ throw new NullPointerException("activity cannot be null");
+ }
+ mNfcActivityManager.setNdefPushContentUriCallback(activity, callback);
}
/**
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
new file mode 100644
index 0000000..8de4e06
--- /dev/null
+++ b/core/java/android/os/NullVibrator.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+/**
+ * Vibrator implementation that does nothing.
+ *
+ * @hide
+ */
+public class NullVibrator extends Vibrator {
+ private static final NullVibrator sInstance = new NullVibrator();
+
+ private NullVibrator() {
+ }
+
+ public static NullVibrator getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return false;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ }
+}
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
new file mode 100644
index 0000000..7c5a47e
--- /dev/null
+++ b/core/java/android/os/SystemVibrator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import android.util.Log;
+
+/**
+ * Vibrator implementation that controls the main system vibrator.
+ *
+ * @hide
+ */
+public class SystemVibrator extends Vibrator {
+ private static final String TAG = "Vibrator";
+
+ private final IVibratorService mService;
+ private final Binder mToken = new Binder();
+
+ public SystemVibrator() {
+ mService = IVibratorService.Stub.asInterface(
+ ServiceManager.getService("vibrator"));
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return false;
+ }
+ try {
+ return mService.hasVibrator();
+ } catch (RemoteException e) {
+ }
+ return false;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return;
+ }
+ try {
+ mService.vibrate(milliseconds, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (mService == null) {
+ Log.w(TAG, "Failed to vibrate; no vibrator service.");
+ return;
+ }
+ // catch this here because the server will do nothing. pattern may
+ // not be null, let that be checked, because the server will drop it
+ // anyway
+ if (repeat < pattern.length) {
+ try {
+ mService.vibratePattern(pattern, repeat, mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to vibrate.", e);
+ }
+ } else {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ if (mService == null) {
+ return;
+ }
+ try {
+ mService.cancelVibrate(mToken);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to cancel vibration.", e);
+ }
+ }
+}
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 3769cfe..3f783c9 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,61 +16,37 @@
package android.os;
-import android.util.Log;
+import android.content.Context;
/**
* Class that operates the vibrator on the device.
* <p>
* If your process exits, any vibration you started with will stop.
* </p>
+ *
+ * To obtain an instance of the system vibrator, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
*/
-public class Vibrator
-{
- private static final String TAG = "Vibrator";
-
- IVibratorService mService;
- private final Binder mToken = new Binder();
-
- /** @hide */
- public Vibrator()
- {
- mService = IVibratorService.Stub.asInterface(
- ServiceManager.getService("vibrator"));
+public abstract class Vibrator {
+ /**
+ * @hide to prevent subclassing from outside of the framework
+ */
+ public Vibrator() {
}
/**
- * Check whether the hardware has a vibrator. Returns true if a vibrator
- * exists, else false.
+ * Check whether the hardware has a vibrator.
+ *
+ * @return True if the hardware has a vibrator, else false.
*/
- public boolean hasVibrator() {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return false;
- }
- try {
- return mService.hasVibrator();
- } catch (RemoteException e) {
- }
- return false;
- }
+ public abstract boolean hasVibrator();
/**
- * Turn the vibrator on.
+ * Vibrate constantly for the specified period of time.
*
* @param milliseconds The number of milliseconds to vibrate.
*/
- public void vibrate(long milliseconds)
- {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- try {
- mService.vibrate(milliseconds, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- }
+ public abstract void vibrate(long milliseconds);
/**
* Vibrate with a given pattern.
@@ -90,38 +66,10 @@
* @param repeat the index into pattern at which to repeat, or -1 if
* you don't want to repeat.
*/
- public void vibrate(long[] pattern, int repeat)
- {
- if (mService == null) {
- Log.w(TAG, "Failed to vibrate; no vibrator service.");
- return;
- }
- // catch this here because the server will do nothing. pattern may
- // not be null, let that be checked, because the server will drop it
- // anyway
- if (repeat < pattern.length) {
- try {
- mService.vibratePattern(pattern, repeat, mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to vibrate.", e);
- }
- } else {
- throw new ArrayIndexOutOfBoundsException();
- }
- }
+ public abstract void vibrate(long[] pattern, int repeat);
/**
* Turn the vibrator off.
*/
- public void cancel()
- {
- if (mService == null) {
- return;
- }
- try {
- mService.cancelVibrate(mToken);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to cancel vibration.", e);
- }
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/provider/BrowserContract.java b/core/java/android/provider/BrowserContract.java
index d678205..118b5eb 100644
--- a/core/java/android/provider/BrowserContract.java
+++ b/core/java/android/provider/BrowserContract.java
@@ -30,6 +30,15 @@
import android.util.Pair;
/**
+ * <p>
+ * The contract between the browser provider and applications. Contains the definition
+ * for the supported URIS and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * BrowserContract defines an database of browser-related information which are bookmarks,
+ * history, images and the mapping between the image and URL.
+ * </p>
* @hide
*/
public class BrowserContract {
@@ -45,12 +54,14 @@
* the dirty flag is not automatically set and the "syncToNetwork" parameter
* is set to false when calling
* {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}.
+ * @hide
*/
public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter";
/**
* A parameter for use when querying any table that allows specifying a limit on the number
* of rows returned.
+ * @hide
*/
public static final String PARAM_LIMIT = "limit";
@@ -58,6 +69,8 @@
* Generic columns for use by sync adapters. The specific functions of
* these columns are private to the sync adapter. Other clients of the API
* should not attempt to either read or write these columns.
+ *
+ * @hide
*/
interface BaseSyncColumns {
/** Generic column for use by sync adapters. */
@@ -74,6 +87,7 @@
/**
* Convenience definitions for use in implementing chrome bookmarks sync in the Bookmarks table.
+ * @hide
*/
public static final class ChromeSyncColumns {
private ChromeSyncColumns() {}
@@ -93,6 +107,7 @@
/**
* Columns that appear when each row of a table belongs to a specific
* account, including sync information that an account may need.
+ * @hide
*/
interface SyncColumns extends BaseSyncColumns {
/**
@@ -144,13 +159,14 @@
public static final String _ID = "_id";
/**
- * The URL of the bookmark.
+ * This column is valid when the row is a URL. The history table's URL
+ * can not be updated.
* <P>Type: TEXT (URL)</P>
*/
public static final String URL = "url";
/**
- * The user visible title of the bookmark.
+ * The user visible title.
* <P>Type: TEXT</P>
*/
public static final String TITLE = "title";
@@ -159,10 +175,14 @@
* The time that this row was created on its originating client (msecs
* since the epoch).
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String DATE_CREATED = "created";
}
+ /**
+ * @hide
+ */
interface ImageColumns {
/**
* The favicon of the bookmark, may be NULL.
@@ -182,7 +202,6 @@
* The touch icon for the web page, may be NULL.
* Must decode via {@link BitmapFactory#decodeByteArray}.
* <p>Type: BLOB (image)</p>
- * @hide
*/
public static final String TOUCH_ICON = "touch_icon";
}
@@ -200,9 +219,26 @@
*/
public static final String VISITS = "visits";
+ /**
+ * @hide
+ */
public static final String USER_ENTERED = "user_entered";
}
+ interface ImageMappingColumns {
+ /**
+ * The ID of the image in Images. One image can map onto the multiple URLs.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String IMAGE_ID = "image_id";
+
+ /**
+ * The URL. The URL can map onto the different type of images.
+ * <P>Type: TEXT (URL)</P>
+ */
+ public static final String URL = "url";
+ }
+
/**
* The bookmarks table, which holds the user's browser bookmarks.
*/
@@ -218,24 +254,71 @@
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "bookmarks");
/**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a bookmark.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK = 1;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is a folder.
+ */
+ public static final int BOOKMARK_TYPE_FOLDER = 2;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is the bookmark bar folder.
+ */
+ public static final int BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER = 3;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder and
+ */
+ public static final int BOOKMARK_TYPE_OTHER_FOLDER = 4;
+
+ /**
+ * Used in {@link Bookmarks#TYPE} column and indicats the row is other folder, .
+ */
+ public static final int BOOKMARK_TYPE_MOBILE_FOLDER = 5;
+
+ /**
+ * The type of the item.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK}</li>
+ * <li>{@link #BOOKMARK_TYPE_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_BOOKMARK_BAR_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_OTHER_FOLDER}</li>
+ * <li>{@link #BOOKMARK_TYPE_MOBILE_FOLDER}</li>
+ * </ul>
+ * </p>
+ * <p> The TYPE_BOOKMARK_BAR_FOLDER, TYPE_OTHER_FOLDER and TYPE_MOBILE_FOLDER
+ * can not be updated or deleted.</p>
+ */
+ public static final String TYPE = "type";
+
+ /**
* The content:// style URI for the default folder
+ * @hide
*/
public static final Uri CONTENT_URI_DEFAULT_FOLDER =
Uri.withAppendedPath(CONTENT_URI, "folder");
/**
* Query parameter used to specify an account name
+ * @hide
*/
public static final String PARAM_ACCOUNT_NAME = "acct_name";
/**
* Query parameter used to specify an account type
+ * @hide
*/
public static final String PARAM_ACCOUNT_TYPE = "acct_type";
/**
* Builds a URI that points to a specific folder.
* @param folderId the ID of the folder to point to
+ * @hide
*/
public static final Uri buildFolderUri(long folderId) {
return ContentUris.withAppendedId(CONTENT_URI_DEFAULT_FOLDER, folderId);
@@ -255,6 +338,7 @@
* Query parameter to use if you want to see deleted bookmarks that are still
* around on the device and haven't been synced yet.
* @see #IS_DELETED
+ * @hide
*/
public static final String QUERY_PARAMETER_SHOW_DELETED = "show_deleted";
@@ -262,6 +346,7 @@
* Flag indicating if an item is a folder or bookmark. Non-zero values indicate
* a folder and zero indicates a bookmark.
* <P>Type: INTEGER (boolean)</P>
+ * @hide
*/
public static final String IS_FOLDER = "folder";
@@ -274,6 +359,7 @@
/**
* The source ID for an item's parent. Read-only.
* @see #PARENT
+ * @hide
*/
public static final String PARENT_SOURCE_ID = "parent_source";
@@ -281,6 +367,7 @@
* The position of the bookmark in relation to it's siblings that share the same
* {@link #PARENT}. May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String POSITION = "position";
@@ -288,6 +375,7 @@
* The item that the bookmark should be inserted after.
* May be negative.
* <P>Type: INTEGER</P>
+ * @hide
*/
public static final String INSERT_AFTER = "insert_after";
@@ -296,6 +384,7 @@
* May be negative.
* <P>Type: INTEGER</P>
* @see #INSERT_AFTER
+ * @hide
*/
public static final String INSERT_AFTER_SOURCE_ID = "insert_after_source";
@@ -305,12 +394,14 @@
* to the URI when performing your query.
* <p>Type: INTEGER (non-zero if the item has been deleted, zero if it hasn't)
* @see #QUERY_PARAMETER_SHOW_DELETED
+ * @hide
*/
public static final String IS_DELETED = "deleted";
}
/**
* Read-only table that lists all the accounts that are used to provide bookmarks.
+ * @hide
*/
public static final class Accounts {
/**
@@ -410,6 +501,7 @@
* A table provided for sync adapters to use for storing private sync state data.
*
* @see SyncStateContract
+ * @hide
*/
public static final class SyncState implements SyncStateContract.Columns {
/**
@@ -459,8 +551,18 @@
}
/**
- * Stores images for URLs. Only support query() and update().
- * @hide
+ * <p>
+ * Stores images for URLs.
+ * </p>
+ * <p>
+ * The rows in this table can not be updated since there might have multiple URLs mapping onto
+ * the same image. If you want to update a URL's image, you need to add the new image in this
+ * table, then update the mapping onto the added image.
+ * </p>
+ * <p>
+ * Every image should be at least associated with one URL, otherwise it will be removed after a
+ * while.
+ * </p>
*/
public static final class Images implements ImageColumns {
/**
@@ -474,15 +576,93 @@
public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "images");
/**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of images.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/images";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/images";
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a favicon.
+ */
+ public static final int IMAGE_TYPE_FAVICON = 1;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a precomposed touch icon.
+ */
+ public static final int IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON = 2;
+
+ /**
+ * Used in {@link Images#TYPE} column and indicats the row is a touch icon.
+ */
+ public static final int IMAGE_TYPE_TOUCH_ICON = 4;
+
+ /**
+ * The type of item in the table.
+ * <P>Type: INTEGER</P>
+ * <p>Allowed values are:</p>
+ * <p>
+ * <ul>
+ * <li>{@link #IMAGE_TYPE_FAVICON}</li>
+ * <li>{@link #IMAGE_TYPE_PRECOMPOSED_TOUCH_ICON}</li>
+ * <li>{@link #IMAGE_TYPE_TOUCH_ICON}</li>
+ * </ul>
+ * </p>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The image data.
+ * <p>Type: BLOB (image)</p>
+ */
+ public static final String DATA = "data";
+
+ /**
* The URL the images came from.
* <P>Type: TEXT (URL)</P>
+ * @hide
*/
public static final String URL = "url_key";
}
/**
+ * <p>
+ * A table that stores the mappings between the image and the URL.
+ * </p>
+ * <p>
+ * Deleting or Updating a mapping might also deletes the mapped image if there is no other URL
+ * maps onto it.
+ * </p>
+ */
+ public static final class ImageMappings implements ImageMappingColumns {
+ /**
+ * This utility class cannot be instantiated
+ */
+ private ImageMappings() {}
+
+ /**
+ * The content:// style URI for this table
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "image_mappings");
+
+ /**
+ * The MIME type of {@link #CONTENT_URI} providing a directory of image mappings.
+ */
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image_mappings";
+
+ /**
+ * The MIME type of a {@link #CONTENT_URI} of a single image mapping.
+ */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/image_mappings";
+ }
+
+ /**
* A combined view of bookmarks and history. All bookmarks in all folders are included and
* no folders are included.
+ * @hide
*/
public static final class Combined implements CommonColumns, HistoryColumns, ImageColumns {
/**
@@ -505,6 +685,7 @@
/**
* A table that stores settings specific to the browser. Only support query and insert.
+ * @hide
*/
public static final class Settings {
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 0e9306b..035d8c4 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -1536,7 +1536,11 @@
*
* @param resolver the ContentResolver to use
* @param contactId the person who was contacted
+ *
+ * @deprecated The class DataUsageStatUpdater of the Android support library should
+ * be used instead.
*/
+ @Deprecated
public static void markAsContacted(ContentResolver resolver, long contactId) {
Uri uri = ContentUris.withAppendedId(CONTENT_URI, contactId);
ContentValues values = new ContentValues();
@@ -7452,7 +7456,7 @@
/**
* <p>
* API allowing applications to send usage information for each {@link Data} row to the
- * Contacts Provider.
+ * Contacts Provider. Applications can also clear all usage information.
* </p>
* <p>
* With the feedback, Contacts Provider may return more contextually appropriate results for
@@ -7497,6 +7501,12 @@
* boolean successful = resolver.update(uri, new ContentValues(), null, null) > 0;
* </pre>
* </p>
+ * <p>
+ * Applications can also clear all usage information with:
+ * <pre>
+ * boolean successful = resolver.delete(DataUsageFeedback.DELETE_USAGE_URI, null, null) > 0;
+ * </pre>
+ * </p>
*/
public static final class DataUsageFeedback {
@@ -7508,6 +7518,14 @@
Uri.withAppendedPath(Data.CONTENT_URI, "usagefeedback");
/**
+ * The content:// style URI for deleting all usage information.
+ * Must be used with {@link ContentResolver#delete(Uri, String, String[])}.
+ * The {@code where} and {@code selectionArgs} parameters are ignored.
+ */
+ public static final Uri DELETE_USAGE_URI =
+ Uri.withAppendedPath(Contacts.CONTENT_URI, "delete_usage");
+
+ /**
* <p>
* Name for query parameter specifying the type of data usage.
* </p>
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index bd6170b..cd8d51f 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -704,6 +704,37 @@
*/
public static final int STATUS_BLOCKED = 498;
+ /** {@hide} */
+ public static String statusToString(int status) {
+ switch (status) {
+ case STATUS_PENDING: return "PENDING";
+ case STATUS_RUNNING: return "RUNNING";
+ case STATUS_PAUSED_BY_APP: return "PAUSED_BY_APP";
+ case STATUS_WAITING_TO_RETRY: return "WAITING_TO_RETRY";
+ case STATUS_WAITING_FOR_NETWORK: return "WAITING_FOR_NETWORK";
+ case STATUS_QUEUED_FOR_WIFI: return "QUEUED_FOR_WIFI";
+ case STATUS_INSUFFICIENT_SPACE_ERROR: return "INSUFFICIENT_SPACE_ERROR";
+ case STATUS_DEVICE_NOT_FOUND_ERROR: return "DEVICE_NOT_FOUND_ERROR";
+ case STATUS_SUCCESS: return "SUCCESS";
+ case STATUS_BAD_REQUEST: return "BAD_REQUEST";
+ case STATUS_NOT_ACCEPTABLE: return "NOT_ACCEPTABLE";
+ case STATUS_LENGTH_REQUIRED: return "LENGTH_REQUIRED";
+ case STATUS_PRECONDITION_FAILED: return "PRECONDITION_FAILED";
+ case STATUS_FILE_ALREADY_EXISTS_ERROR: return "FILE_ALREADY_EXISTS_ERROR";
+ case STATUS_CANNOT_RESUME: return "CANNOT_RESUME";
+ case STATUS_CANCELED: return "CANCELED";
+ case STATUS_UNKNOWN_ERROR: return "UNKNOWN_ERROR";
+ case STATUS_FILE_ERROR: return "FILE_ERROR";
+ case STATUS_UNHANDLED_REDIRECT: return "UNHANDLED_REDIRECT";
+ case STATUS_UNHANDLED_HTTP_CODE: return "UNHANDLED_HTTP_CODE";
+ case STATUS_HTTP_DATA_ERROR: return "HTTP_DATA_ERROR";
+ case STATUS_HTTP_EXCEPTION: return "HTTP_EXCEPTION";
+ case STATUS_TOO_MANY_REDIRECTS: return "TOO_MANY_REDIRECTS";
+ case STATUS_BLOCKED: return "BLOCKED";
+ default: return Integer.toString(status);
+ }
+ }
+
/**
* This download is visible but only shows in the notifications
* while it's in progress.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2aaf548..6dfbb2f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1464,6 +1464,20 @@
public static final String VIBRATE_ON = "vibrate_on";
/**
+ * If 1, redirects the system vibrator to all currently attached input devices
+ * that support vibration. If there are no such input devices, then the system
+ * vibrator is used instead.
+ * If 0, does not register the system vibrator.
+ *
+ * This setting is mainly intended to provide a compatibility mechanism for
+ * applications that only know about the system vibrator and do not use the
+ * input device vibrator API.
+ *
+ * @hide
+ */
+ public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
+
+ /**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*/
@@ -1970,6 +1984,7 @@
SCREEN_BRIGHTNESS_MODE,
SCREEN_AUTO_BRIGHTNESS_ADJ,
VIBRATE_ON,
+ VIBRATE_INPUT_DEVICES,
MODE_RINGER,
MODE_RINGER_STREAMS_AFFECTED,
MUTE_STREAMS_AFFECTED,
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 8c97293..35e2e4a 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -147,6 +147,15 @@
return out.toString();
}
+ /**
+ * Returns an HTML escaped representation of the given plain text.
+ */
+ public static String escapeHtml(CharSequence text) {
+ StringBuilder out = new StringBuilder();
+ withinStyle(out, text, 0, text.length());
+ return out.toString();
+ }
+
private static void withinHtml(StringBuilder out, Spanned text) {
int len = text.length();
@@ -370,7 +379,7 @@
}
}
- private static void withinStyle(StringBuilder out, Spanned text,
+ private static void withinStyle(StringBuilder out, CharSequence text,
int start, int end) {
for (int i = start; i < end; i++) {
char c = text.charAt(i);
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 6056c75..11c169e 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -75,7 +75,7 @@
if (spans[i] instanceof NoCopySpan) {
continue;
}
-
+
int st = sp.getSpanStart(spans[i]) - start;
int en = sp.getSpanEnd(spans[i]) - start;
int fl = sp.getSpanFlags(spans[i]);
@@ -212,7 +212,7 @@
if (mGapLength > 2 * length())
resizeFor(length());
-
+
return ret; // == this
}
@@ -220,7 +220,7 @@
public void clear() {
replace(0, length(), "", 0, 0);
}
-
+
// Documentation from interface
public void clearSpans() {
for (int i = mSpanCount - 1; i >= 0; i--) {
@@ -257,45 +257,50 @@
return append(String.valueOf(text));
}
- private void change(int start, int end, CharSequence tb, int tbstart, int tbend) {
- checkRange("replace", start, end);
+ private void change(int start, int end, CharSequence cs, int csStart, int csEnd) {
+ // Can be negative
+ final int nbNewChars = (csEnd - csStart) - (end - start);
for (int i = mSpanCount - 1; i >= 0; i--) {
+ int spanStart = mSpanStarts[i];
+ if (spanStart > mGapStart)
+ spanStart -= mGapLength;
+
+ int spanEnd = mSpanEnds[i];
+ if (spanEnd > mGapStart)
+ spanEnd -= mGapLength;
+
if ((mSpanFlags[i] & SPAN_PARAGRAPH) == SPAN_PARAGRAPH) {
- int st = mSpanStarts[i];
- if (st > mGapStart)
- st -= mGapLength;
-
- int en = mSpanEnds[i];
- if (en > mGapStart)
- en -= mGapLength;
-
- int ost = st;
- int oen = en;
+ int ost = spanStart;
+ int oen = spanEnd;
int clen = length();
- if (st > start && st <= end) {
- for (st = end; st < clen; st++)
- if (st > end && charAt(st - 1) == '\n')
+ if (spanStart > start && spanStart <= end) {
+ for (spanStart = end; spanStart < clen; spanStart++)
+ if (spanStart > end && charAt(spanStart - 1) == '\n')
break;
}
- if (en > start && en <= end) {
- for (en = end; en < clen; en++)
- if (en > end && charAt(en - 1) == '\n')
+ if (spanEnd > start && spanEnd <= end) {
+ for (spanEnd = end; spanEnd < clen; spanEnd++)
+ if (spanEnd > end && charAt(spanEnd - 1) == '\n')
break;
}
- if (st != ost || en != oen)
- setSpan(false, mSpans[i], st, en, mSpanFlags[i]);
+ if (spanStart != ost || spanEnd != oen)
+ setSpan(false, mSpans[i], spanStart, spanEnd, mSpanFlags[i]);
}
+
+ int flags = 0;
+ if (spanStart == start) flags |= SPAN_START_AT_START;
+ else if (spanStart == end + nbNewChars) flags |= SPAN_START_AT_END;
+ if (spanEnd == start) flags |= SPAN_END_AT_START;
+ else if (spanEnd == end + nbNewChars) flags |= SPAN_END_AT_END;
+ mSpanFlags[i] |= flags;
}
moveGapTo(end);
- // Can be negative
- final int nbNewChars = (tbend - tbstart) - (end - start);
-
if (nbNewChars >= mGapLength) {
resizeFor(mText.length + nbNewChars - mGapLength);
}
@@ -306,7 +311,7 @@
if (mGapLength < 1)
new Exception("mGapLength < 1").printStackTrace();
- TextUtils.getChars(tb, tbstart, tbend, mText, start);
+ TextUtils.getChars(cs, csStart, csEnd, mText, start);
if (end > start) {
// no need for span fixup on pure insertion
@@ -340,21 +345,23 @@
}
}
- if (tb instanceof Spanned) {
- Spanned sp = (Spanned) tb;
- Object[] spans = sp.getSpans(tbstart, tbend, Object.class);
+ mSpanCountBeforeAdd = mSpanCount;
+
+ if (cs instanceof Spanned) {
+ Spanned sp = (Spanned) cs;
+ Object[] spans = sp.getSpans(csStart, csEnd, Object.class);
for (int i = 0; i < spans.length; i++) {
int st = sp.getSpanStart(spans[i]);
int en = sp.getSpanEnd(spans[i]);
- if (st < tbstart) st = tbstart;
- if (en > tbend) en = tbend;
+ if (st < csStart) st = csStart;
+ if (en > csEnd) en = csEnd;
// Add span only if this object is not yet used as a span in this string
- if (getSpanStart(spans[i]) < 0) {
- setSpan(false, spans[i], st - tbstart + start, en - tbstart + start,
- sp.getSpanFlags(spans[i]));
+ if (getSpanStart(spans[i]) < 0 && !(spans[i] instanceof SpanWatcher)) {
+ setSpan(false, spans[i], st - csStart + start, en - csStart + start,
+ sp.getSpanFlags(spans[i]));
}
}
}
@@ -390,6 +397,8 @@
// Documentation from interface
public SpannableStringBuilder replace(final int start, final int end,
CharSequence tb, int tbstart, int tbend) {
+ checkRange("replace", start, end);
+
int filtercount = mFilters.length;
for (int i = 0; i < filtercount; i++) {
CharSequence repl = mFilters[i].filter(tb, tbstart, tbend, this, start, end);
@@ -404,10 +413,6 @@
final int origLen = end - start;
final int newLen = tbend - tbstart;
- if (origLen == 0 && newLen == 0) {
- return this;
- }
-
TextWatcher[] textWatchers = getSpans(start, start + origLen, TextWatcher.class);
sendBeforeTextChanged(textWatchers, start, origLen, newLen);
@@ -415,43 +420,101 @@
// a text replacement. If replaced or replacement text length is zero, this
// is already taken care of.
boolean adjustSelection = origLen != 0 && newLen != 0;
- int selstart = 0;
- int selend = 0;
+ int selectionStart = 0;
+ int selectionEnd = 0;
if (adjustSelection) {
- selstart = Selection.getSelectionStart(this);
- selend = Selection.getSelectionEnd(this);
+ selectionStart = Selection.getSelectionStart(this);
+ selectionEnd = Selection.getSelectionEnd(this);
}
- checkRange("replace", start, end);
-
change(start, end, tb, tbstart, tbend);
if (adjustSelection) {
- if (selstart > start && selstart < end) {
- long off = selstart - start;
+ if (selectionStart > start && selectionStart < end) {
+ final int offset = (selectionStart - start) * newLen / origLen;
+ selectionStart = start + offset;
- off = off * newLen / origLen;
- selstart = (int) off + start;
-
- setSpan(false, Selection.SELECTION_START, selstart, selstart,
+ setSpan(false, Selection.SELECTION_START, selectionStart, selectionStart,
Spanned.SPAN_POINT_POINT);
}
- if (selend > start && selend < end) {
- long off = selend - start;
+ if (selectionEnd > start && selectionEnd < end) {
+ final int offset = (selectionEnd - start) * newLen / origLen;
+ selectionEnd = start + offset;
- off = off * newLen / origLen;
- selend = (int) off + start;
-
- setSpan(false, Selection.SELECTION_END, selend, selend, Spanned.SPAN_POINT_POINT);
+ setSpan(false, Selection.SELECTION_END, selectionEnd, selectionEnd,
+ Spanned.SPAN_POINT_POINT);
}
}
sendTextChanged(textWatchers, start, origLen, newLen);
sendAfterTextChanged(textWatchers);
+ // Span watchers need to be called after text watchers, which may update the layout
+ sendToSpanWatchers(start, end, newLen - origLen);
+
return this;
}
+ private void sendToSpanWatchers(int replaceStart, int replaceEnd, int nbNewChars) {
+ for (int i = 0; i < mSpanCountBeforeAdd; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ int spanFlags = mSpanFlags[i];
+
+ int newReplaceEnd = replaceEnd + nbNewChars;
+ boolean spanChanged = false;
+ int previousSpanStart = spanStart;
+ if (spanStart > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanStart -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanStart >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanStart != replaceStart ||
+ ((spanFlags & SPAN_START_AT_START) != SPAN_START_AT_START)) &&
+ (spanStart != newReplaceEnd ||
+ ((spanFlags & SPAN_START_AT_END) != SPAN_START_AT_END))) {
+ // TODO previousSpanStart is incorrect, but we would need to save all the
+ // previous spans' positions before replace to provide it
+ spanChanged = true;
+ }
+ }
+ int previousSpanEnd = spanEnd;
+ if (spanEnd > newReplaceEnd) {
+ if (nbNewChars != 0) {
+ previousSpanEnd -= nbNewChars;
+ spanChanged = true;
+ }
+ } else if (spanEnd >= replaceStart) {
+ // No change if span start was already at replace interval boundaries before replace
+ if ((spanEnd != replaceStart ||
+ ((spanFlags & SPAN_END_AT_START) != SPAN_END_AT_START)) &&
+ (spanEnd != newReplaceEnd ||
+ ((spanFlags & SPAN_END_AT_END) != SPAN_END_AT_END))) {
+ // TODO same as above for previousSpanEnd
+ spanChanged = true;
+ }
+ }
+
+ if (spanChanged) {
+ sendSpanChanged(mSpans[i], previousSpanStart, previousSpanEnd, spanStart, spanEnd);
+ }
+ mSpanFlags[i] &= ~SPAN_START_END_MASK;
+ }
+
+ // The spans starting at mIntermediateSpanCount were added from the replacement text
+ for (int i = mSpanCountBeforeAdd; i < mSpanCount; i++) {
+ int spanStart = mSpanStarts[i];
+ int spanEnd = mSpanEnds[i];
+ if (spanStart > mGapStart) spanStart -= mGapLength;
+ if (spanEnd > mGapStart) spanEnd -= mGapLength;
+ sendSpanAdded(mSpans[i], spanStart, spanEnd);
+ }
+ }
+
/**
* Mark the specified range of text with the specified object.
* The flags determine how the span will behave when text is
@@ -788,13 +851,12 @@
if (end <= mGapStart) {
System.arraycopy(mText, start, dest, destoff, end - start);
} else if (start >= mGapStart) {
- System.arraycopy(mText, start + mGapLength,
- dest, destoff, end - start);
+ System.arraycopy(mText, start + mGapLength, dest, destoff, end - start);
} else {
System.arraycopy(mText, start, dest, destoff, mGapStart - start);
System.arraycopy(mText, mGapStart + mGapLength,
- dest, destoff + (mGapStart - start),
- end - mGapStart);
+ dest, destoff + (mGapStart - start),
+ end - mGapStart);
}
}
@@ -863,12 +925,14 @@
}
}
- private void sendSpanChanged(Object what, int s, int e, int st, int en) {
- SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en), SpanWatcher.class);
- int n = recip.length;
-
+ private void sendSpanChanged(Object what, int oldStart, int oldEnd, int start, int end) {
+ // The bounds of a possible SpanWatcher are guaranteed to be set before this method is
+ // called, so that the order of the span does not affect this broadcast.
+ SpanWatcher[] spanWatchers = getSpans(Math.min(oldStart, start),
+ Math.min(Math.max(oldEnd, end), length()), SpanWatcher.class);
+ int n = spanWatchers.length;
for (int i = 0; i < n; i++) {
- recip[i].onSpanChanged(this, what, s, e, st, en);
+ spanWatchers[i].onSpanChanged(this, what, oldStart, oldEnd, start, end);
}
}
@@ -879,26 +943,23 @@
private void checkRange(final String operation, int start, int end) {
if (end < start) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " has end before start");
+ region(start, end) + " has end before start");
}
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " ends beyond length " + len);
+ region(start, end) + " ends beyond length " + len);
}
if (start < 0 || end < 0) {
throw new IndexOutOfBoundsException(operation + " " +
- region(start, end) +
- " starts before 0");
+ region(start, end) + " starts before 0");
}
}
-/*
+ /*
private boolean isprint(char c) { // XXX
if (c >= ' ' && c <= '~')
return true;
@@ -977,7 +1038,7 @@
System.out.print("\n");
}
-*/
+ */
/**
* Don't call this yourself -- exists for Canvas to use internally.
@@ -1023,7 +1084,7 @@
}
}
- /**
+ /**
* Don't call this yourself -- exists for Paint to use internally.
* {@hide}
*/
@@ -1059,8 +1120,7 @@
if (end <= mGapStart) {
ret = p.getTextWidths(mText, start, end - start, widths);
} else if (start >= mGapStart) {
- ret = p.getTextWidths(mText, start + mGapLength, end - start,
- widths);
+ ret = p.getTextWidths(mText, start + mGapLength, end - start, widths);
} else {
char[] buf = TextUtils.obtain(end - start);
@@ -1205,6 +1265,7 @@
private int[] mSpanEnds;
private int[] mSpanFlags;
private int mSpanCount;
+ private int mSpanCountBeforeAdd;
// TODO These value are tightly related to the public SPAN_MARK/POINT values in {@link Spanned}
private static final int MARK = 1;
@@ -1214,4 +1275,11 @@
private static final int START_MASK = 0xF0;
private static final int END_MASK = 0x0F;
private static final int START_SHIFT = 4;
+
+ // These bits are not (currently) used by SPANNED flags
+ private static final int SPAN_START_AT_START = 0x1000;
+ private static final int SPAN_START_AT_END = 0x2000;
+ private static final int SPAN_END_AT_START = 0x4000;
+ private static final int SPAN_END_AT_END = 0x8000;
+ private static final int SPAN_START_END_MASK = 0xF000;
}
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
new file mode 100644
index 0000000..ab21b32
--- /dev/null
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -0,0 +1,900 @@
+/*
+ * 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.
+ * 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.view;
+
+import static android.view.accessibility.AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Pool;
+import android.util.Poolable;
+import android.util.PoolableManager;
+import android.util.Pools;
+import android.util.SparseLongArray;
+import android.view.ViewGroup.ChildListForAccessibility;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeProvider;
+import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class for managing accessibility interactions initiated from the system
+ * and targeting the view hierarchy. A *ClientThread method is to be
+ * called from the interaction connection ViewAncestor gives the system to
+ * talk to it and a corresponding *UiThread method that is executed on the
+ * UI thread.
+ */
+final class AccessibilityInteractionController {
+ private static final int POOL_SIZE = 5;
+
+ private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
+ new ArrayList<AccessibilityNodeInfo>();
+
+ private final Handler mHandler = new PrivateHandler();
+
+ private final ViewRootImpl mViewRootImpl;
+
+ private final AccessibilityNodePrefetcher mPrefetcher;
+
+ public AccessibilityInteractionController(ViewRootImpl viewRootImpl) {
+ mViewRootImpl = viewRootImpl;
+ mPrefetcher = new AccessibilityNodePrefetcher();
+ }
+
+ // Reusable poolable arguments for interacting with the view hierarchy
+ // to fit more arguments than Message and to avoid sharing objects between
+ // two messages since several threads can send messages concurrently.
+ private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
+ new PoolableManager<SomeArgs>() {
+ public SomeArgs newInstance() {
+ return new SomeArgs();
+ }
+
+ public void onAcquired(SomeArgs info) {
+ /* do nothing */
+ }
+
+ public void onReleased(SomeArgs info) {
+ info.clear();
+ }
+ }, POOL_SIZE)
+ );
+
+ private class SomeArgs implements Poolable<SomeArgs> {
+ private SomeArgs mNext;
+ private boolean mIsPooled;
+
+ public Object arg1;
+ public Object arg2;
+ public int argi1;
+ public int argi2;
+ public int argi3;
+
+ public SomeArgs getNextPoolable() {
+ return mNext;
+ }
+
+ public boolean isPooled() {
+ return mIsPooled;
+ }
+
+ public void setNextPoolable(SomeArgs args) {
+ mNext = args;
+ }
+
+ public void setPooled(boolean isPooled) {
+ mIsPooled = isPooled;
+ }
+
+ private void clear() {
+ arg1 = null;
+ arg2 = null;
+ argi1 = 0;
+ argi2 = 0;
+ argi3 = 0;
+ }
+ }
+
+ public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
+ long accessibilityNodeId, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
+ message.arg1 = flags;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
+ final int flags = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
+ root = mViewRootImpl.mView;
+ } else {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ mPrefetcher.prefetchAccessibilityNodeInfos(root, virtualDescendantId, flags, infos);
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ infos.clear();
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
+ int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = viewId;
+ args.argi2 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int viewId = args.argi1;
+ final int interactionId = args.argi2;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo info = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null) {
+ View target = root.findViewById(viewId);
+ if (target != null && isDisplayedOnScreen(target)) {
+ info = target.createAccessibilityNodeInfo();
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
+ String text, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ int flags, int interrogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
+ message.arg1 = flags;
+ SomeArgs args = mPool.acquire();
+ args.arg1 = text;
+ args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi3 = interactionId;
+ args.arg2 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interrogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findAccessibilityNodeInfosByTextUiThread(Message message) {
+ final int flags = message.arg1;
+ SomeArgs args = (SomeArgs) message.obj;
+ final String text = (String) args.arg1;
+ final int accessibilityViewId = args.argi1;
+ final int virtualDescendantId = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg2;
+ mPool.release(args);
+ List<AccessibilityNodeInfo> infos = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+ if (provider != null) {
+ infos = provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
+ ArrayList<View> foundViews = mViewRootImpl.mAttachInfo.mTempArrayList;
+ foundViews.clear();
+ root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
+ | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
+ | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
+ if (!foundViews.isEmpty()) {
+ infos = mTempAccessibilityNodeInfoList;
+ infos.clear();
+ final int viewCount = foundViews.size();
+ for (int i = 0; i < viewCount; i++) {
+ View foundView = foundViews.get(i);
+ if (isDisplayedOnScreen(foundView)) {
+ provider = foundView.getAccessibilityNodeProvider();
+ if (provider != null) {
+ List<AccessibilityNodeInfo> infosFromProvider =
+ provider.findAccessibilityNodeInfosByText(text,
+ virtualDescendantId);
+ if (infosFromProvider != null) {
+ infos.addAll(infosFromProvider);
+ }
+ } else {
+ infos.add(foundView.createAccessibilityNodeInfo());
+ }
+ }
+ }
+ }
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void findFocusClientThread(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FIND_FOCUS;
+ message.arg1 = flags;
+ message.arg2 = focusType;
+ SomeArgs args = mPool.acquire();
+ args.argi1 = interactionId;
+ args.argi2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ args.argi3 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void findFocusUiThread(Message message) {
+ final int flags = message.arg1;
+ final int focusType = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int interactionId = args.argi1;
+ final int accessibilityViewId = args.argi2;
+ final int virtualDescendantId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo focused = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ switch (focusType) {
+ case AccessibilityNodeInfo.FOCUS_ACCESSIBILITY: {
+ View host = mViewRootImpl.mAccessibilityFocusedHost;
+ // If there is no accessibility focus host or it is not a descendant
+ // of the root from which to start the search, then the search failed.
+ if (host == null || !ViewRootImpl.isViewDescendantOf(host, root)) {
+ break;
+ }
+ // If the host has a provider ask this provider to search for the
+ // focus instead fetching all provider nodes to do the search here.
+ AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
+ if (provider != null) {
+ focused = provider.findAccessibilitiyFocus(virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ focused = host.createAccessibilityNodeInfo();
+ }
+ } break;
+ case AccessibilityNodeInfo.FOCUS_INPUT: {
+ // Input focus cannot go to virtual views.
+ View target = root.findFocus();
+ if (target != null && isDisplayedOnScreen(target)) {
+ focused = target.createAccessibilityNodeInfo();
+ }
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown focus type: " + focusType);
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(focused, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void focusSearchClientThread(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interogatingPid,
+ long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_FOCUS_SEARCH;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi2 = direction;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void focusSearchUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final int direction = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ AccessibilityNodeInfo next = null;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View root = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ root = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ root = mViewRootImpl.mView;
+ }
+ if (root != null && isDisplayedOnScreen(root)) {
+ if ((direction & View.FOCUS_ACCESSIBILITY) == View.FOCUS_ACCESSIBILITY) {
+ AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
+ if (provider != null) {
+ next = provider.accessibilityFocusSearch(direction,
+ virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ // If the focus search reached a node with a provider
+ // we delegate to the provider to find the next one.
+ provider = nextView.getAccessibilityNodeProvider();
+ if (provider != null) {
+ next = provider.accessibilityFocusSearch(direction,
+ virtualDescendantId);
+ } else {
+ next = nextView.createAccessibilityNodeInfo();
+ }
+ }
+ }
+ } else {
+ View nextView = root.focusSearch(direction);
+ if (nextView != null) {
+ next = nextView.createAccessibilityNodeInfo();
+ }
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setFindAccessibilityNodeInfoResult(next, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
+ int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
+ int interogatingPid, long interrogatingTid) {
+ Message message = mHandler.obtainMessage();
+ message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
+ message.arg1 = flags;
+ message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
+ SomeArgs args = mPool.acquire();
+ args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
+ args.argi2 = action;
+ args.argi3 = interactionId;
+ args.arg1 = callback;
+ message.obj = args;
+ // If the interrogation is performed by the same thread as the main UI
+ // thread in this process, set the message as a static reference so
+ // after this call completes the same thread but in the interrogating
+ // client can handle the message to generate the result.
+ if (interogatingPid == Process.myPid()
+ && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
+ AccessibilityInteractionClient.getInstanceForThread(
+ interrogatingTid).setSameThreadMessage(message);
+ } else {
+ mHandler.sendMessage(message);
+ }
+ }
+
+ private void perfromAccessibilityActionUiThread(Message message) {
+ final int flags = message.arg1;
+ final int accessibilityViewId = message.arg2;
+ SomeArgs args = (SomeArgs) message.obj;
+ final int virtualDescendantId = args.argi1;
+ final int action = args.argi2;
+ final int interactionId = args.argi3;
+ final IAccessibilityInteractionConnectionCallback callback =
+ (IAccessibilityInteractionConnectionCallback) args.arg1;
+ mPool.release(args);
+ boolean succeeded = false;
+ try {
+ if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
+ return;
+ }
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews =
+ (flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ View target = null;
+ if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ target = findViewByAccessibilityId(accessibilityViewId);
+ } else {
+ target = mViewRootImpl.mView;
+ }
+ if (target != null && isDisplayedOnScreen(target)) {
+ AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
+ if (provider != null) {
+ succeeded = provider.performAccessibilityAction(action, virtualDescendantId);
+ } else if (virtualDescendantId == View.NO_ID) {
+ succeeded = target.performAccessibilityAction(action);
+ }
+ }
+ } finally {
+ try {
+ mViewRootImpl.mAttachInfo.mIncludeNotImportantViews = false;
+ callback.setPerformAccessibilityActionResult(succeeded, interactionId);
+ } catch (RemoteException re) {
+ /* ignore - the other side will time out */
+ }
+ }
+ }
+
+ private View findViewByAccessibilityId(int accessibilityId) {
+ View root = mViewRootImpl.mView;
+ if (root == null) {
+ return null;
+ }
+ View foundView = root.findViewByAccessibilityId(accessibilityId);
+ if (foundView != null && !isDisplayedOnScreen(foundView)) {
+ return null;
+ }
+ return foundView;
+ }
+
+ /**
+ * Computes whether a view is visible on the screen.
+ *
+ * @param view The view to check.
+ * @return Whether the view is visible on the screen.
+ */
+ private boolean isDisplayedOnScreen(View view) {
+ // The first two checks are made also made by isShown() which
+ // however traverses the tree up to the parent to catch that.
+ // Therefore, we do some fail fast check to minimize the up
+ // tree traversal.
+ return (view.mAttachInfo != null
+ && view.mAttachInfo.mWindowVisibility == View.VISIBLE
+ && view.isShown()
+ && view.getGlobalVisibleRect(mViewRootImpl.mTempRect));
+ }
+
+ /**
+ * This class encapsulates a prefetching strategy for the accessibility APIs for
+ * querying window content. It is responsible to prefetch a batch of
+ * AccessibilityNodeInfos in addition to the one for a requested node.
+ */
+ private class AccessibilityNodePrefetcher {
+
+ private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
+
+ public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
+ List<AccessibilityNodeInfo> outInfos) {
+ AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfRealNode(view, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfRealNode(view, outInfos);
+ }
+ }
+ } else {
+ AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
+ if (root != null) {
+ outInfos.add(root);
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
+ prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
+ prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
+ }
+ if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
+ prefetchDescendantsOfVirtualNode(root, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfRealNode(View view,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = view.getParentForAccessibility();
+ while (parent instanceof View
+ && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ View parentView = (View) parent;
+ final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
+ parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
+ AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ }
+ parent = parent.getParentForAccessibility();
+ }
+ }
+
+ private void prefetchSiblingsOfRealNode(View current,
+ List<AccessibilityNodeInfo> outInfos) {
+ ViewParent parent = current.getParentForAccessibility();
+ if (parent instanceof ViewGroup) {
+ ViewGroup parentGroup = (ViewGroup) parent;
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(parentGroup,
+ false);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ children.recycle();
+ return;
+ }
+ View child = children.getChildAt(i);
+ if (child.getAccessibilityViewId() != current.getAccessibilityViewId()
+ && isDisplayedOnScreen(child)) {
+ AccessibilityNodeInfo info = null;
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ info = child.createAccessibilityNodeInfo();
+ } else {
+ info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ }
+ if (info != null) {
+ outInfos.add(info);
+ }
+ }
+ }
+ children.recycle();
+ }
+ }
+
+ private void prefetchDescendantsOfRealNode(View root,
+ List<AccessibilityNodeInfo> outInfos) {
+ if (!(root instanceof ViewGroup)) {
+ return;
+ }
+ ViewGroup rootGroup = (ViewGroup) root;
+ HashMap<View, AccessibilityNodeInfo> addedChildren =
+ new HashMap<View, AccessibilityNodeInfo>();
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(rootGroup, false);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ children.recycle();
+ return;
+ }
+ View child = children.getChildAt(i);
+ if ( isDisplayedOnScreen(child)) {
+ AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
+ if (provider == null) {
+ AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, null);
+ }
+ } else {
+ AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.UNDEFINED);
+ if (info != null) {
+ outInfos.add(info);
+ addedChildren.put(child, info);
+ }
+ }
+ }
+ }
+ children.recycle();
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
+ View addedChild = entry.getKey();
+ AccessibilityNodeInfo virtualRoot = entry.getValue();
+ if (virtualRoot == null) {
+ prefetchDescendantsOfRealNode(addedChild, outInfos);
+ } else {
+ AccessibilityNodeProvider provider =
+ addedChild.getAccessibilityNodeProvider();
+ prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
+ View providerHost, AccessibilityNodeProvider provider,
+ List<AccessibilityNodeInfo> outInfos) {
+ long parentNodeId = root.getParentNodeId();
+ int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final int virtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || accessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
+ virtualDescendantId);
+ if (parent != null) {
+ outInfos.add(parent);
+ }
+ parentNodeId = parent.getParentNodeId();
+ accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
+ parentNodeId);
+ } else {
+ prefetchPredecessorsOfRealNode(providerHost, outInfos);
+ return;
+ }
+ }
+ }
+
+ private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ final long parentNodeId = current.getParentNodeId();
+ final int parentAccessibilityViewId =
+ AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
+ final int parentVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
+ if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
+ || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
+ AccessibilityNodeInfo parent =
+ provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
+ if (parent != null) {
+ SparseLongArray childNodeIds = parent.getChildNodeIds();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = childNodeIds.get(i);
+ if (childNodeId != current.getSourceNodeId()) {
+ final int childVirtualDescendantId =
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ childVirtualDescendantId);
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ }
+ }
+ } else {
+ prefetchSiblingsOfRealNode(providerHost, outInfos);
+ }
+ }
+
+ private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
+ AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
+ SparseLongArray childNodeIds = root.getChildNodeIds();
+ final int initialOutInfosSize = outInfos.size();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ if (outInfos.size() >= MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ return;
+ }
+ final long childNodeId = childNodeIds.get(i);
+ AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
+ AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
+ if (child != null) {
+ outInfos.add(child);
+ }
+ }
+ if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
+ final int addedChildCount = outInfos.size() - initialOutInfosSize;
+ for (int i = 0; i < addedChildCount; i++) {
+ AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
+ prefetchDescendantsOfVirtualNode(child, provider, outInfos);
+ }
+ }
+ }
+ }
+
+ private class PrivateHandler extends Handler {
+ private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 3;
+ private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4;
+ private final static int MSG_FIND_FOCUS = 5;
+ private final static int MSG_FOCUS_SEARCH = 6;
+
+ public PrivateHandler() {
+ super(Looper.getMainLooper());
+ }
+
+ @Override
+ public String getMessageName(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_PERFORM_ACCESSIBILITY_ACTION:
+ return "MSG_PERFORM_ACCESSIBILITY_ACTION";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
+ return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
+ case MSG_FIND_FOCUS:
+ return "MSG_FIND_FOCUS";
+ case MSG_FOCUS_SEARCH:
+ return "MSG_FOCUS_SEARCH";
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int type = message.what;
+ switch (type) {
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
+ findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
+ } break;
+ case MSG_PERFORM_ACCESSIBILITY_ACTION: {
+ perfromAccessibilityActionUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
+ findAccessibilityNodeInfoByViewIdUiThread(message);
+ } break;
+ case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
+ findAccessibilityNodeInfosByTextUiThread(message);
+ } break;
+ case MSG_FIND_FOCUS: {
+ findFocusUiThread(message);
+ } break;
+ case MSG_FOCUS_SEARCH: {
+ focusSearchUiThread(message);
+ } break;
+ default:
+ throw new IllegalArgumentException("Unknown message type: " + type);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/FocusFinder.java b/core/java/android/view/FocusFinder.java
index 3529b8e..8a01c15 100644
--- a/core/java/android/view/FocusFinder.java
+++ b/core/java/android/view/FocusFinder.java
@@ -17,10 +17,12 @@
package android.view;
import android.graphics.Rect;
+import android.view.ViewGroup.ChildListForAccessibility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
+import java.util.Stack;
/**
* The algorithm used for finding the next focusable view in a given direction
@@ -30,7 +32,7 @@
private static ThreadLocal<FocusFinder> tlFocusFinder =
new ThreadLocal<FocusFinder>() {
-
+ @Override
protected FocusFinder initialValue() {
return new FocusFinder();
}
@@ -48,6 +50,10 @@
Rect mBestCandidateRect = new Rect();
SequentialFocusComparator mSequentialFocusComparator = new SequentialFocusComparator();
+ private final ArrayList<View> mTempList = new ArrayList<View>();
+
+ private Stack<View> mTempStack;
+
// enforce thread local access
private FocusFinder() {}
@@ -60,7 +66,30 @@
* @return The next focusable view, or null if none exists.
*/
public final View findNextFocus(ViewGroup root, View focused, int direction) {
+ return findNextFocus(root, focused, mFocusedRect, direction);
+ }
+ /**
+ * Find the next view to take focus in root's descendants, searching from
+ * a particular rectangle in root's coordinates.
+ * @param root Contains focusedRect. Cannot be null.
+ * @param focusedRect The starting point of the search.
+ * @param direction Direction to look.
+ * @return The next focusable view, or null if none exists.
+ */
+ public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
+ return findNextFocus(root, null, focusedRect, direction);
+ }
+
+ private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
+ if ((direction & View.FOCUS_ACCESSIBILITY) != View.FOCUS_ACCESSIBILITY) {
+ return findNextInputFocus(root, focused, focusedRect, direction);
+ } else {
+ return findNextAccessibilityFocus(root, focused, direction);
+ }
+ }
+
+ private View findNextInputFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
if (focused != null) {
// check for user specified next focus
View userSetNextFocus = focused.findUserSetNextFocus(root, direction);
@@ -79,90 +108,154 @@
switch (direction) {
case View.FOCUS_RIGHT:
case View.FOCUS_DOWN:
- setFocusBottomRight(root);
+ setFocusTopLeft(root);
break;
case View.FOCUS_FORWARD:
if (root.isLayoutRtl()) {
- setFocusTopLeft(root);
- } else {
setFocusBottomRight(root);
+ } else {
+ setFocusTopLeft(root);
}
break;
case View.FOCUS_LEFT:
case View.FOCUS_UP:
- setFocusTopLeft(root);
+ setFocusBottomRight(root);
break;
case View.FOCUS_BACKWARD:
if (root.isLayoutRtl()) {
- setFocusBottomRight(root);
- } else {
setFocusTopLeft(root);
+ } else {
+ setFocusBottomRight(root);
break;
}
}
}
- return findNextFocus(root, focused, mFocusedRect, direction);
+
+ ArrayList<View> focusables = mTempList;
+ focusables.clear();
+ root.addFocusables(focusables, direction);
+ if (focusables.isEmpty()) {
+ // The focus cannot change.
+ return null;
+ }
+
+ try {
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ return findNextInputFocusInRelativeDirection(focusables, root, focused,
+ focusedRect, direction);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return findNextInputFocusInAbsoluteDirection(focusables, root, focused,
+ focusedRect, direction);
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ } finally {
+ focusables.clear();
+ }
}
- private void setFocusTopLeft(ViewGroup root) {
+ /**
+ * Find the next view to take accessibility focus in root's descendants,
+ * starting from the view that currently is accessibility focused.
+ *
+ * @param root The root which also contains the focused view.
+ * @param focused The current accessibility focused view.
+ * @param direction Direction to look.
+ * @return The next focusable view, or null if none exists.
+ */
+ private View findNextAccessibilityFocus(ViewGroup root, View focused, int direction) {
+ switch (direction) {
+ case View.ACCESSIBILITY_FOCUS_IN:
+ case View.ACCESSIBILITY_FOCUS_OUT:
+ case View.ACCESSIBILITY_FOCUS_FORWARD:
+ case View.ACCESSIBILITY_FOCUS_BACKWARD: {
+ return findNextHierarchicalAcessibilityFocus(root, focused, direction);
+ }
+ case View.ACCESSIBILITY_FOCUS_LEFT:
+ case View.ACCESSIBILITY_FOCUS_RIGHT:
+ case View.ACCESSIBILITY_FOCUS_UP:
+ case View.ACCESSIBILITY_FOCUS_DOWN: {
+ return findNextDirectionalAccessibilityFocus(root, focused, direction);
+ }
+ default:
+ throw new IllegalArgumentException("Unknown direction: " + direction);
+ }
+ }
+
+ private View findNextHierarchicalAcessibilityFocus(ViewGroup root, View focused,
+ int direction) {
+ View current = (focused != null) ? focused : root;
+ switch (direction) {
+ case View.ACCESSIBILITY_FOCUS_IN: {
+ return findNextAccessibilityFocusIn(current);
+ }
+ case View.ACCESSIBILITY_FOCUS_OUT: {
+ return findNextAccessibilityFocusOut(current);
+ }
+ case View.ACCESSIBILITY_FOCUS_FORWARD: {
+ return findNextAccessibilityFocusForward(current);
+ }
+ case View.ACCESSIBILITY_FOCUS_BACKWARD: {
+ return findNextAccessibilityFocusBackward(current);
+ }
+ }
+ return null;
+ }
+
+ private View findNextDirectionalAccessibilityFocus(ViewGroup root, View focused,
+ int direction) {
+ ArrayList<View> focusables = mTempList;
+ focusables.clear();
+ root.addFocusables(focusables, direction, View.FOCUSABLES_ACCESSIBILITY);
+ Rect focusedRect = getFocusedRect(root, focused, direction);
+ final int inputFocusDirection = getCorrespondingInputFocusDirection(direction);
+ View next = findNextInputFocusInAbsoluteDirection(focusables, root,
+ focused, focusedRect, inputFocusDirection);
+ focusables.clear();
+ return next;
+ }
+
+ private View findNextInputFocusInRelativeDirection(ArrayList<View> focusables, ViewGroup root,
+ View focused, Rect focusedRect, int direction) {
+ try {
+ // Note: This sort is stable.
+ mSequentialFocusComparator.setRoot(root);
+ Collections.sort(focusables, mSequentialFocusComparator);
+ } finally {
+ mSequentialFocusComparator.recycle();
+ }
+
+ final int count = focusables.size();
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ return getForwardFocusable(root, focused, focusables, count);
+ case View.FOCUS_BACKWARD:
+ return getBackwardFocusable(root, focused, focusables, count);
+ }
+ return focusables.get(count - 1);
+ }
+
+ private void setFocusBottomRight(ViewGroup root) {
final int rootBottom = root.getScrollY() + root.getHeight();
final int rootRight = root.getScrollX() + root.getWidth();
mFocusedRect.set(rootRight, rootBottom,
rootRight, rootBottom);
}
- private void setFocusBottomRight(ViewGroup root) {
+ private void setFocusTopLeft(ViewGroup root) {
final int rootTop = root.getScrollY();
final int rootLeft = root.getScrollX();
mFocusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
}
- /**
- * Find the next view to take focus in root's descendants, searching from
- * a particular rectangle in root's coordinates.
- * @param root Contains focusedRect. Cannot be null.
- * @param focusedRect The starting point of the search.
- * @param direction Direction to look.
- * @return The next focusable view, or null if none exists.
- */
- public View findNextFocusFromRect(ViewGroup root, Rect focusedRect, int direction) {
- return findNextFocus(root, null, focusedRect, direction);
- }
-
- private View findNextFocus(ViewGroup root, View focused, Rect focusedRect, int direction) {
- ArrayList<View> focusables = root.getFocusables(direction);
- if (focusables.isEmpty()) {
- // The focus cannot change.
- return null;
- }
-
- if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
- if (focused != null && !focusables.contains(focused)) {
- // Add the currently focused view to the list to have it sorted
- // along with the other views.
- focusables.add(focused);
- }
-
- try {
- // Note: This sort is stable.
- mSequentialFocusComparator.setRoot(root);
- Collections.sort(focusables, mSequentialFocusComparator);
- } finally {
- mSequentialFocusComparator.recycle();
- }
-
- final int count = focusables.size();
- switch (direction) {
- case View.FOCUS_FORWARD:
- return getForwardFocusable(root, focused, focusables, count);
-
- case View.FOCUS_BACKWARD:
- return getBackwardFocusable(root, focused, focusables, count);
- }
- return null;
- }
-
+ View findNextInputFocusInAbsoluteDirection(ArrayList<View> focusables, ViewGroup root, View focused,
+ Rect focusedRect, int direction) {
// initialize the best candidate to something impossible
// (so the first plausible view will become the best choice)
mBestCandidateRect.set(focusedRect);
@@ -201,6 +294,140 @@
return closest;
}
+ private View findNextAccessibilityFocusIn(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ if (mTempStack == null) {
+ mTempStack = new Stack<View>();
+ }
+ Stack<View> fringe = mTempStack;
+ fringe.clear();
+ fringe.add(view);
+ while (!fringe.isEmpty()) {
+ View current = fringe.pop();
+ if (current.getAccessibilityNodeProvider() != null) {
+ fringe.clear();
+ return current;
+ }
+ if (current != view && current.includeForAccessibility()) {
+ fringe.clear();
+ return current;
+ }
+ if (current instanceof ViewGroup) {
+ ViewGroup currentGroup = (ViewGroup) current;
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ currentGroup, true);
+ final int childCount = children.getChildCount();
+ for (int i = childCount - 1; i >= 0; i--) {
+ fringe.push(children.getChildAt(i));
+ }
+ children.recycle();
+ }
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusOut(View view) {
+ ViewParent parent = view.getParentForAccessibility();
+ if (parent instanceof View) {
+ return (View) parent;
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusForward(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ View current = view;
+ while (current != null) {
+ ViewParent parent = current.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup parentGroup = (ViewGroup) parent;
+ // Ask the parent to find a sibling after the current view
+ // that can take accessibility focus.
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ parentGroup, true);
+ final int fromIndex = children.getChildIndex(current) + 1;
+ final int childCount = children.getChildCount();
+ for (int i = fromIndex; i < childCount; i++) {
+ View child = children.getChildAt(i);
+ View next = null;
+ if (child.getAccessibilityNodeProvider() != null) {
+ next = child;
+ } else if (child.includeForAccessibility()) {
+ next = child;
+ } else {
+ next = findNextAccessibilityFocusIn(child);
+ }
+ if (next != null) {
+ children.recycle();
+ return next;
+ }
+ }
+ children.recycle();
+ // Reaching a regarded for accessibility predecessor without
+ // finding a next view to take focus means that at this level
+ // there is no next accessibility focusable sibling.
+ if (parentGroup.includeForAccessibility()) {
+ return null;
+ }
+ // Try asking a predecessor to find a focusable.
+ current = parentGroup;
+ }
+ return null;
+ }
+
+ private View findNextAccessibilityFocusBackward(View view) {
+ // We have to traverse the full view tree to make sure
+ // we consider views in the order specified by their
+ // parent layout managers since some managers could be
+ // LTR while some could be RTL.
+ View current = view;
+ while (current != null) {
+ ViewParent parent = current.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return null;
+ }
+ ViewGroup parentGroup = (ViewGroup) parent;
+ // Ask the parent to find a sibling after the current view
+ // to take accessibility focus
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(
+ parentGroup, true);
+ final int fromIndex = children.getChildIndex(current) - 1;
+ for (int i = fromIndex; i >= 0; i--) {
+ View child = children.getChildAt(i);
+ View next = null;
+ if (child.getAccessibilityNodeProvider() != null) {
+ next = child;
+ } else if (child.includeForAccessibility()) {
+ next = child;
+ } else {
+ next = findNextAccessibilityFocusIn(child);
+ }
+ if (next != null) {
+ children.recycle();
+ return next;
+ }
+ }
+ children.recycle();
+ // Reaching a regarded for accessibility predecessor without
+ // finding a previous view to take focus means that at this level
+ // there is no previous accessibility focusable sibling.
+ if (parentGroup.includeForAccessibility()) {
+ return null;
+ }
+ // Try asking a predecessor to find a focusable.
+ current = parentGroup;
+ }
+ return null;
+ }
+
private static View getForwardFocusable(ViewGroup root, View focused,
ArrayList<View> focusables, int count) {
return (root.isLayoutRtl()) ?
@@ -235,6 +462,47 @@
return focusables.get(count - 1);
}
+ private Rect getFocusedRect(ViewGroup root, View focused, int direction) {
+ Rect focusedRect = mFocusedRect;
+ if (focused != null) {
+ focused.getFocusedRect(focusedRect);
+ root.offsetDescendantRectToMyCoords(focused, focusedRect);
+ } else {
+ switch (direction) {
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_DOWN:
+ final int rootTop = root.getScrollY();
+ final int rootLeft = root.getScrollX();
+ focusedRect.set(rootLeft, rootTop, rootLeft, rootTop);
+ break;
+
+ case View.FOCUS_LEFT:
+ case View.FOCUS_UP:
+ final int rootBottom = root.getScrollY() + root.getHeight();
+ final int rootRight = root.getScrollX() + root.getWidth();
+ focusedRect.set(rootRight, rootBottom, rootRight, rootBottom);
+ break;
+ }
+ }
+ return focusedRect;
+ }
+
+ private int getCorrespondingInputFocusDirection(int accessFocusDirection) {
+ switch (accessFocusDirection) {
+ case View.ACCESSIBILITY_FOCUS_LEFT:
+ return View.FOCUS_LEFT;
+ case View.ACCESSIBILITY_FOCUS_RIGHT:
+ return View.FOCUS_RIGHT;
+ case View.ACCESSIBILITY_FOCUS_UP:
+ return View.FOCUS_UP;
+ case View.ACCESSIBILITY_FOCUS_DOWN:
+ return View.FOCUS_DOWN;
+ default:
+ throw new IllegalArgumentException("Cannot map accessiblity focus"
+ + " direction: " + accessFocusDirection);
+ }
+ }
+
/**
* Is rect1 a better candidate than rect2 for a focus search in a particular
* direction from a source rect? This is the core routine that determines
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index aa0ac74..8bc36b7 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -237,6 +237,17 @@
abstract boolean validate();
/**
+ * This method ensures the hardware renderer is in a valid state
+ * before executing the specified action.
+ *
+ * This method will attempt to set a valid state even if the window
+ * the renderer is attached to was destroyed.
+ *
+ * @return true if the action was run
+ */
+ abstract boolean safelyRun(Runnable action);
+
+ /**
* Setup the hardware renderer for drawing. This is called whenever the
* size of the target surface changes or when the surface is first created.
*
@@ -1380,26 +1391,40 @@
}
@Override
- void destroyHardwareResources(View view) {
- if (view != null) {
- boolean needsContext = true;
- if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
+ boolean safelyRun(Runnable action) {
+ boolean needsContext = true;
+ if (isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) needsContext = false;
- if (needsContext) {
- Gl20RendererEglContext managedContext =
- (Gl20RendererEglContext) sEglContextStorage.get();
- if (managedContext == null) return;
- usePbufferSurface(managedContext.getContext());
- }
+ if (needsContext) {
+ Gl20RendererEglContext managedContext =
+ (Gl20RendererEglContext) sEglContextStorage.get();
+ if (managedContext == null) return false;
+ usePbufferSurface(managedContext.getContext());
+ }
- destroyResources(view);
- GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
-
+ try {
+ action.run();
+ } finally {
if (needsContext) {
sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE,
EGL_NO_SURFACE, EGL_NO_CONTEXT);
}
}
+
+ return true;
+ }
+
+ @Override
+ void destroyHardwareResources(final View view) {
+ if (view != null) {
+ safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroyResources(view);
+ GLES20Canvas.flushCaches(GLES20Canvas.FLUSH_CACHES_LAYERS);
+ }
+ });
+ }
}
private static void destroyResources(View view) {
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 75b2c746..4848a7a 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,9 +16,12 @@
package android.view;
+import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Vibrator;
+import android.os.NullVibrator;
import java.util.ArrayList;
import java.util.List;
@@ -40,13 +43,17 @@
*/
public final class InputDevice implements Parcelable {
private final int mId;
+ private final int mGeneration;
private final String mName;
private final String mDescriptor;
private final int mSources;
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
+ private final boolean mHasVibrator;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+ private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+
/**
* A mask for input source classes.
*
@@ -302,23 +309,27 @@
};
// Called by native code.
- private InputDevice(int id, String name, String descriptor, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap) {
+ private InputDevice(int id, int generation, String name, String descriptor, int sources,
+ int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator) {
mId = id;
+ mGeneration = generation;
mName = name;
mDescriptor = descriptor;
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
+ mHasVibrator = hasVibrator;
}
private InputDevice(Parcel in) {
mId = in.readInt();
+ mGeneration = in.readInt();
mName = in.readString();
mDescriptor = in.readString();
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+ mHasVibrator = in.readInt() != 0;
for (;;) {
int axis = in.readInt();
@@ -364,6 +375,19 @@
}
/**
+ * Gets a generation number for this input device.
+ * The generation number is incremented whenever the device is reconfigured and its
+ * properties may have changed.
+ *
+ * @return The generation number.
+ *
+ * @hide
+ */
+ public int getGeneration() {
+ return mGeneration;
+ }
+
+ /**
* Gets the input device descriptor, which is a stable identifier for an input device.
* <p>
* An input device descriptor uniquely identifies an input device. Its value
@@ -506,6 +530,31 @@
}
/**
+ * Gets the vibrator service associated with the device, if there is one.
+ * Even if the device does not have a vibrator, the result is never null.
+ * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
+ * present.
+ *
+ * Note that the vibrator associated with the device may be different from
+ * the system vibrator. To obtain an instance of the system vibrator instead, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
+ *
+ * @return The vibrator service associated with the device, never null.
+ */
+ public Vibrator getVibrator() {
+ synchronized (mMotionRanges) {
+ if (mVibrator == null) {
+ if (mHasVibrator) {
+ mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId);
+ } else {
+ mVibrator = NullVibrator.getInstance();
+ }
+ }
+ return mVibrator;
+ }
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
@@ -595,11 +644,13 @@
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mId);
+ out.writeInt(mGeneration);
out.writeString(mName);
out.writeString(mDescriptor);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
+ out.writeInt(mHasVibrator ? 1 : 0);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -624,6 +675,7 @@
StringBuilder description = new StringBuilder();
description.append("Input Device ").append(mId).append(": ").append(mName).append("\n");
description.append(" Descriptor: ").append(mDescriptor).append("\n");
+ description.append(" Generation: ").append(mGeneration).append("\n");
description.append(" Keyboard Type: ");
switch (mKeyboardType) {
@@ -639,6 +691,8 @@
}
description.append("\n");
+ description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 3d165ea..12d7b12 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -22,8 +22,6 @@
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
import android.hardware.input.InputManager;
-import android.util.SparseArray;
-import android.view.InputDevice.MotionRange;
import java.lang.Character;
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 3cd8b71..214dc5c 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -187,7 +187,9 @@
public void setOpaque(boolean opaque) {
if (opaque != mOpaque) {
mOpaque = opaque;
- updateLayer();
+ if (mLayer != null) {
+ updateLayer();
+ }
}
}
@@ -204,7 +206,18 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- destroySurface();
+ if (mLayer != null && mAttachInfo != null && mAttachInfo.mHardwareRenderer != null) {
+ boolean success = mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+ @Override
+ public void run() {
+ destroySurface();
+ }
+ });
+
+ if (!success) {
+ Log.w(LOG_TAG, "TextureView was not able to destroy its surface: " + this);
+ }
+ }
}
private void destroySurface() {
@@ -663,7 +676,6 @@
*
* @param surfaceTexture The {@link SurfaceTexture} that the view should use.
* @see SurfaceTexture#detachFromGLContext()
- * @hide
*/
public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
if (surfaceTexture == null) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0be7a87..d569ba1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -24,6 +24,7 @@
import android.graphics.Bitmap;
import android.graphics.Camera;
import android.graphics.Canvas;
+import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
@@ -975,6 +976,14 @@
public static final int FOCUSABLES_TOUCH_MODE = 0x00000001;
/**
+ * View flag indicating whether {@link #addFocusables(ArrayList, int, int)}
+ * should add only accessibility focusable Views.
+ *
+ * @hide
+ */
+ public static final int FOCUSABLES_ACCESSIBILITY = 0x00000002;
+
+ /**
* Use with {@link #focusSearch(int)}. Move focus to the previous selectable
* item.
*/
@@ -1006,6 +1015,54 @@
*/
public static final int FOCUS_DOWN = 0x00000082;
+ // Accessibility focus directions.
+
+ /**
+ * The accessibility focus which is the current user position when
+ * interacting with the accessibility framework.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 0x00001000;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus left.
+ */
+ public static final int ACCESSIBILITY_FOCUS_LEFT = FOCUS_LEFT | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus up.
+ */
+ public static final int ACCESSIBILITY_FOCUS_UP = FOCUS_UP | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus right.
+ */
+ public static final int ACCESSIBILITY_FOCUS_RIGHT = FOCUS_RIGHT | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus down.
+ */
+ public static final int ACCESSIBILITY_FOCUS_DOWN = FOCUS_DOWN | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus to the next view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_FORWARD = FOCUS_FORWARD | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus to the previous view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_BACKWARD = FOCUS_BACKWARD | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus in a view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_IN = 0x00000004 | FOCUS_ACCESSIBILITY;
+
+ /**
+ * Use with {@link #focusSearch(int)}. Move acessibility focus out of a view.
+ */
+ public static final int ACCESSIBILITY_FOCUS_OUT = 0x00000008 | FOCUS_ACCESSIBILITY;
+
/**
* Bits of {@link #getMeasuredWidthAndState()} and
* {@link #getMeasuredWidthAndState()} that provide the actual measured size.
@@ -1330,7 +1387,7 @@
R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
R.attr.state_hovered, VIEW_STATE_HOVERED,
R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
- R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
};
static {
@@ -1452,7 +1509,8 @@
| AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
| AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
| AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
- | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED;
+ | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
/**
* Temporary Rect currently for use in setBackground(). This will probably
@@ -1784,7 +1842,6 @@
*/
private static final int LAYOUT_DIRECTION_DEFAULT = LAYOUT_DIRECTION_INHERIT;
-
/**
* Indicates that the view is tracking some sort of transient state
* that the app should not need to be aware of, but that the framework
@@ -1992,6 +2049,50 @@
public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT =
TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+ // Accessiblity constants for mPrivateFlags2
+
+ /**
+ * Shift for accessibility related bits in {@link #mPrivateFlags2}.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_SHIFT = 20;
+
+ /**
+ * Automatically determine whether a view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0x00000000;
+
+ /**
+ * The view is important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 0x00000001;
+
+ /**
+ * The view is not important for accessibility.
+ */
+ public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 0x00000002;
+
+ /**
+ * The default whether the view is important for accessiblity.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_DEFAULT = IMPORTANT_FOR_ACCESSIBILITY_AUTO;
+
+ /**
+ * Mask for obtainig the bits which specify how to determine
+ * whether a view is important for accessibility.
+ */
+ static final int IMPORTANT_FOR_ACCESSIBILITY_MASK = (IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ | IMPORTANT_FOR_ACCESSIBILITY_YES | IMPORTANT_FOR_ACCESSIBILITY_NO)
+ << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+ /**
+ * Flag indicating whether a view has accessibility focus.
+ */
+ static final int ACCESSIBILITY_FOCUSED = 0x00000040 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+
+ /**
+ * Flag indicating whether a view state for accessibility has changed.
+ */
+ static final int ACCESSIBILITY_STATE_CHANGED = 0x00000080 << IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
/* End of masks for mPrivateFlags2 */
@@ -2598,6 +2699,12 @@
protected int mPaddingBottom;
/**
+ * The layout insets in pixels, that is the distance in pixels between the
+ * visible edges of this view its bounds.
+ */
+ private Insets mLayoutInsets;
+
+ /**
* Briefly describes the view and is primarily used for accessibility support.
*/
private CharSequence mContentDescription;
@@ -2952,7 +3059,8 @@
// Set layout and text direction defaults
mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) |
(TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) |
- (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT);
+ (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT) |
+ (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
mUserPaddingStart = -1;
@@ -3340,6 +3448,9 @@
final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT);
mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment];
break;
+ case R.styleable.View_importantForAccessibility:
+ setImportantForAccessibility(a.getInt(attr,
+ IMPORTANT_FOR_ACCESSIBILITY_DEFAULT));
}
}
@@ -3970,6 +4081,10 @@
onFocusChanged(true, direction, previouslyFocusedRect);
refreshDrawableState();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -4050,16 +4165,21 @@
}
onFocusChanged(false, 0, null);
+
refreshDrawableState();
ensureInputFocusOnFirstFocusable();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
void ensureInputFocusOnFirstFocusable() {
View root = getRootView();
if (root != null) {
- root.requestFocus(FOCUS_FORWARD);
+ root.requestFocus();
}
}
@@ -4077,6 +4197,10 @@
onFocusChanged(false, 0, null);
refreshDrawableState();
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -4127,7 +4251,10 @@
*/
protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
if (gainFocus) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ requestAccessibilityFocus();
+ }
}
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -4237,7 +4364,7 @@
*/
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
- mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
+ mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
} else {
sendAccessibilityEventUncheckedInternal(event);
}
@@ -4257,6 +4384,31 @@
if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
dispatchPopulateAccessibilityEvent(event);
}
+ // Intercept accessibility focus events fired by virtual nodes to keep
+ // track of accessibility focus position in such nodes.
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
+ final long virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ event.getSourceNodeId());
+ if (virtualNodeId != AccessibilityNodeInfo.UNDEFINED) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(this);
+ }
+ }
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
+ final long virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(
+ event.getSourceNodeId());
+ if (virtualNodeId != AccessibilityNodeInfo.UNDEFINED) {
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(null);
+ }
+ }
+ } break;
+ }
// In the beginning we called #isShown(), so we know that getParent() is not null.
getParent().requestSendAccessibilityEvent(this, event);
}
@@ -4399,7 +4551,7 @@
event.setContentDescription(mContentDescription);
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && mAttachInfo != null) {
- ArrayList<View> focusablesTempList = mAttachInfo.mFocusablesTempList;
+ ArrayList<View> focusablesTempList = mAttachInfo.mTempArrayList;
getRootView().addFocusables(focusablesTempList, View.FOCUS_FORWARD,
FOCUSABLES_ALL);
event.setItemCount(focusablesTempList.size());
@@ -4483,17 +4635,14 @@
getDrawingRect(bounds);
info.setBoundsInParent(bounds);
- int[] locationOnScreen = mAttachInfo.mInvalidateChildLocation;
- getLocationOnScreen(locationOnScreen);
- bounds.offsetTo(0, 0);
- bounds.offset(locationOnScreen[0], locationOnScreen[1]);
+ getGlobalVisibleRect(bounds);
+ bounds.offset(mAttachInfo.mWindowLeft, mAttachInfo.mWindowTop);
info.setBoundsInScreen(bounds);
if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
- ViewParent parent = getParent();
+ ViewParent parent = getParentForAccessibility();
if (parent instanceof View) {
- View parentView = (View) parent;
- info.setParent(parentView);
+ info.setParent((View) parent);
}
}
@@ -4505,6 +4654,7 @@
info.setClickable(isClickable());
info.setFocusable(isFocusable());
info.setFocused(isFocused());
+ info.setAccessibilityFocused(isAccessibilityFocused());
info.setSelected(isSelected());
info.setLongClickable(isLongClickable());
@@ -4599,10 +4749,11 @@
* true for views that do not have textual representation (For example,
* ImageButton).
*
- * @return The content descriptiopn.
+ * @return The content description.
*
* @attr ref android.R.styleable#View_contentDescription
*/
+ @ViewDebug.ExportedProperty(category = "accessibility")
public CharSequence getContentDescription() {
return mContentDescription;
}
@@ -5652,8 +5803,9 @@
* Adds any focusable views that are descendants of this view (possibly
* including this view if it is focusable itself) to views. This method
* adds all focusable views regardless if we are in touch mode or
- * only views focusable in touch mode if we are in touch mode depending on
- * the focusable mode paramater.
+ * only views focusable in touch mode if we are in touch mode or
+ * only views that can take accessibility focus if accessibility is enabeld
+ * depending on the focusable mode paramater.
*
* @param views Focusable views found so far or null if all we are interested is
* the number of focusables.
@@ -5662,19 +5814,32 @@
*
* @see #FOCUSABLES_ALL
* @see #FOCUSABLES_TOUCH_MODE
+ * @see #FOCUSABLES_ACCESSIBILITY
*/
public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
- if (!isFocusable()) {
+ if (views == null) {
return;
}
-
- if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
- isInTouchMode() && !isFocusableInTouchMode()) {
- return;
+ if ((focusableMode & FOCUSABLE_IN_TOUCH_MODE) == FOCUSABLE_IN_TOUCH_MODE) {
+ if (isFocusable() && (!isInTouchMode() || isFocusableInTouchMode())) {
+ views.add(this);
+ return;
+ }
}
-
- if (views != null) {
- views.add(this);
+ if ((focusableMode & FOCUSABLES_ACCESSIBILITY) == FOCUSABLES_ACCESSIBILITY) {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && includeForAccessibility()) {
+ views.add(this);
+ return;
+ }
+ }
+ if ((focusableMode & FOCUSABLES_ALL) == FOCUSABLES_ALL) {
+ if (isFocusable()) {
+ views.add(this);
+ return;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknow focusable mode: " + focusableMode);
}
}
@@ -5736,6 +5901,149 @@
}
/**
+ * Returns whether this View is accessibility focused.
+ *
+ * @return True if this View is accessibility focused.
+ */
+ boolean isAccessibilityFocused() {
+ return (mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ /**
+ * Call this to try to give accessibility focus to this view.
+ *
+ * A view will not actually take focus if {@link AccessibilityManager#isEnabled()}
+ * returns false or the view is no visible or the view already has accessibility
+ * focus.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @return Whether this view actually took accessibility focus.
+ *
+ * @hide
+ */
+ public boolean requestAccessibilityFocus() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return false;
+ }
+ if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
+ return false;
+ }
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) == 0) {
+ mPrivateFlags2 |= ACCESSIBILITY_FOCUSED;
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(this);
+ }
+ invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+ notifyAccessibilityStateChanged();
+ // Try to give input focus to this view - not a descendant.
+ requestFocusNoSearch(View.FOCUS_DOWN, null);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Call this to try to clear accessibility focus of this view.
+ *
+ * See also {@link #focusSearch(int)}, which is what you call to say that you
+ * have focus, and you want your parent to look for the next one.
+ *
+ * @hide
+ */
+ public void clearAccessibilityFocus() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ ViewRootImpl viewRootImpl = getViewRootImpl();
+ if (viewRootImpl != null) {
+ viewRootImpl.setAccessibilityFocusedHost(null);
+ }
+ invalidate();
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ notifyAccessibilityStateChanged();
+ // Try to move accessibility focus to the input focus.
+ View rootView = getRootView();
+ if (rootView != null) {
+ View inputFocus = rootView.findFocus();
+ if (inputFocus != null) {
+ inputFocus.requestAccessibilityFocus();
+ }
+ }
+ }
+ }
+
+ /**
+ * Find the best view to take accessibility focus from a hover.
+ * This function finds the deepest actionable view and if that
+ * fails ask the parent to take accessibility focus from hover.
+ *
+ * @param x The X hovered location in this view coorditantes.
+ * @param y The Y hovered location in this view coorditantes.
+ * @return Whether the request was handled.
+ *
+ * @hide
+ */
+ public boolean requestAccessibilityFocusFromHover(float x, float y) {
+ if (onRequestAccessibilityFocusFromHover(x, y)) {
+ return true;
+ }
+ ViewParent parent = mParent;
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+
+ float[] position = mAttachInfo.mTmpTransformLocation;
+ position[0] = x;
+ position[1] = y;
+
+ // Compensate for the transformation of the current matrix.
+ if (!hasIdentityMatrix()) {
+ getMatrix().mapPoints(position);
+ }
+
+ // Compensate for the parent scroll and the offset
+ // of this view stop from the parent top.
+ position[0] += mLeft - parentView.mScrollX;
+ position[1] += mTop - parentView.mScrollY;
+
+ return parentView.requestAccessibilityFocusFromHover(position[0], position[1]);
+ }
+ return false;
+ }
+
+ /**
+ * Requests to give this View focus from hover.
+ *
+ * @param x The X hovered location in this view coorditantes.
+ * @param y The Y hovered location in this view coorditantes.
+ * @return Whether the request was handled.
+ *
+ * @hide
+ */
+ public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
+ if (includeForAccessibility()
+ && (isActionableForAccessibility() || hasListenersForAccessibility())) {
+ return requestAccessibilityFocus();
+ }
+ return false;
+ }
+
+ /**
+ * Clears accessibility focus without calling any callback methods
+ * normally invoked in {@link #clearAccessibilityFocus()}. This method
+ * is used for clearing accessibility focus when giving this focus to
+ * another view.
+ */
+ void clearAccessibilityFocusNoCallbacks() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_FOCUSED) != 0) {
+ mPrivateFlags2 &= ~ACCESSIBILITY_FOCUSED;
+ invalidate();
+ }
+ }
+
+ /**
* Call this to try to give focus to a specific view or to one of its
* descendants.
*
@@ -5755,7 +6063,6 @@
return requestFocus(View.FOCUS_DOWN);
}
-
/**
* Call this to try to give focus to a specific view or to one of its
* descendants and give it a hint about what direction focus is heading.
@@ -5807,6 +6114,10 @@
* @return Whether this view or one of its descendants actually took focus.
*/
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
+ return requestFocusNoSearch(direction, previouslyFocusedRect);
+ }
+
+ private boolean requestFocusNoSearch(int direction, Rect previouslyFocusedRect) {
// need to be focusable
if ((mViewFlags & FOCUSABLE_MASK) != FOCUSABLE ||
(mViewFlags & VISIBILITY_MASK) != VISIBLE) {
@@ -5866,6 +6177,248 @@
}
/**
+ * Gets the mode for determining whether this View is important for accessibility
+ * which is if it fires accessibility events and if it is reported to
+ * accessibility services that query the screen.
+ *
+ * @return The mode for determining whether a View is important for accessibility.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ @ViewDebug.ExportedProperty(category = "accessibility", mapping = {
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_AUTO,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_AUTO"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_YES,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_YES"),
+ @ViewDebug.IntToString(from = IMPORTANT_FOR_ACCESSIBILITY_NO,
+ to = "IMPORTANT_FOR_ACCESSIBILITY_NO")
+ })
+ public int getImportantForAccessibility() {
+ return (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ }
+
+ /**
+ * Sets how to determine whether this view is important for accessibility
+ * which is if it fires accessibility events and if it is reported to
+ * accessibility services that query the screen.
+ *
+ * @param mode How to determine whether this view is important for accessibility.
+ *
+ * @attr ref android.R.styleable#View_importantForAccessibility
+ *
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_YES
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_NO
+ * @see #IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ */
+ public void setImportantForAccessibility(int mode) {
+ if (mode != getImportantForAccessibility()) {
+ mPrivateFlags2 &= ~IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ mPrivateFlags2 |= (mode << IMPORTANT_FOR_ACCESSIBILITY_SHIFT)
+ & IMPORTANT_FOR_ACCESSIBILITY_MASK;
+ notifyAccessibilityStateChanged();
+ }
+ }
+
+ /**
+ * Gets whether this view should be exposed for accessibility.
+ *
+ * @return Whether the view is exposed for accessibility.
+ *
+ * @hide
+ */
+ public boolean isImportantForAccessibility() {
+ final int mode = (mPrivateFlags2 & IMPORTANT_FOR_ACCESSIBILITY_MASK)
+ >> IMPORTANT_FOR_ACCESSIBILITY_SHIFT;
+ switch (mode) {
+ case IMPORTANT_FOR_ACCESSIBILITY_YES:
+ return true;
+ case IMPORTANT_FOR_ACCESSIBILITY_NO:
+ return false;
+ case IMPORTANT_FOR_ACCESSIBILITY_AUTO:
+ return isActionableForAccessibility() || hasListenersForAccessibility();
+ default:
+ throw new IllegalArgumentException("Unknow important for accessibility mode: "
+ + mode);
+ }
+ }
+
+ /**
+ * Gets the parent for accessibility purposes. Note that the parent for
+ * accessibility is not necessary the immediate parent. It is the first
+ * predecessor that is important for accessibility.
+ *
+ * @return The parent for accessibility purposes.
+ */
+ public ViewParent getParentForAccessibility() {
+ if (mParent instanceof View) {
+ View parentView = (View) mParent;
+ if (parentView.includeForAccessibility()) {
+ return mParent;
+ } else {
+ return mParent.getParentForAccessibility();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds the children of a given View for accessibility. Since some Views are
+ * not important for accessibility the children for accessibility are not
+ * necessarily direct children of the riew, rather they are the first level of
+ * descendants important for accessibility.
+ *
+ * @param children The list of children for accessibility.
+ */
+ public void addChildrenForAccessibility(ArrayList<View> children) {
+ if (includeForAccessibility()) {
+ children.add(this);
+ }
+ }
+
+ /**
+ * Whether to regard this view for accessibility. A view is regarded for
+ * accessibility if it is important for accessibility or the querying
+ * accessibility service has explicitly requested that view not
+ * important for accessibility are regarded.
+ *
+ * @return Whether to regard the view for accessibility.
+ */
+ boolean includeForAccessibility() {
+ if (mAttachInfo != null) {
+ if (!mAttachInfo.mIncludeNotImportantViews) {
+ return isImportantForAccessibility();
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns whether the View is considered actionable from
+ * accessibility perspective. Such view are important for
+ * accessiiblity.
+ *
+ * @return True if the view is actionable for accessibility.
+ */
+ private boolean isActionableForAccessibility() {
+ return (isClickable() || isLongClickable() || isFocusable());
+ }
+
+ /**
+ * Returns whether the View has registered callbacks wich makes it
+ * important for accessiiblity.
+ *
+ * @return True if the view is actionable for accessibility.
+ */
+ private boolean hasListenersForAccessibility() {
+ ListenerInfo info = getListenerInfo();
+ return mTouchDelegate != null || info.mOnKeyListener != null
+ || info.mOnTouchListener != null || info.mOnGenericMotionListener != null
+ || info.mOnHoverListener != null || info.mOnDragListener != null;
+ }
+
+ /**
+ * Notifies accessibility services that some view's important for
+ * accessibility state has changed. Note that such notifications
+ * are made at most once every
+ * {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}
+ * to avoid unnecessary load to the system. Also once a view has
+ * made a notifucation this method is a NOP until the notification has
+ * been sent to clients.
+ *
+ * @hide
+ *
+ * TODO: Makse sure this method is called for any view state change
+ * that is interesting for accessilility purposes.
+ */
+ public void notifyAccessibilityStateChanged() {
+ if ((mPrivateFlags2 & ACCESSIBILITY_STATE_CHANGED) == 0) {
+ mPrivateFlags2 |= ACCESSIBILITY_STATE_CHANGED;
+ if (mParent != null) {
+ mParent.childAccessibilityStateChanged(this);
+ }
+ }
+ }
+
+ /**
+ * Reset the state indicating the this view has requested clients
+ * interested in its accessiblity state to be notified.
+ *
+ * @hide
+ */
+ public void resetAccessibilityStateChanged() {
+ mPrivateFlags2 &= ~ACCESSIBILITY_STATE_CHANGED;
+ }
+
+ /**
+ * Performs the specified accessibility action on the view. For
+ * possible accessibility actions look at {@link AccessibilityNodeInfo}.
+ *
+ * @param action The action to perform.
+ * @return Whether the action was performed.
+ */
+ public boolean performAccessibilityAction(int action) {
+ switch (action) {
+ case AccessibilityNodeInfo.ACTION_CLICK: {
+ final long now = SystemClock.uptimeMillis();
+ // Send down.
+ MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN,
+ getWidth() / 2, getHeight() / 2, 0);
+ onTouchEvent(event);
+ // Send up.
+ event.setAction(MotionEvent.ACTION_UP);
+ onTouchEvent(event);
+ // Clean up.
+ event.recycle();
+ } break;
+ case AccessibilityNodeInfo.ACTION_FOCUS: {
+ if (!hasFocus()) {
+ // Get out of touch mode since accessibility
+ // wants to move focus around.
+ getViewRootImpl().ensureTouchMode(false);
+ return requestFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
+ if (hasFocus()) {
+ clearFocus();
+ return !isFocused();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_SELECT: {
+ if (!isSelected()) {
+ setSelected(true);
+ return isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
+ if (isSelected()) {
+ setSelected(false);
+ return !isSelected();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {
+ if (!isAccessibilityFocused()) {
+ return requestAccessibilityFocus();
+ }
+ } break;
+ case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {
+ if (isAccessibilityFocused()) {
+ clearAccessibilityFocus();
+ return true;
+ }
+ } break;
+ }
+ return false;
+ }
+
+ /**
* @hide
*/
public void dispatchStartTemporaryDetach() {
@@ -6759,21 +7312,27 @@
// The root view may receive hover (or touch) events that are outside the bounds of
// the window. This code ensures that we only send accessibility events for
// hovers that are actually within the bounds of the root view.
- final int action = event.getAction();
+ final int action = event.getActionMasked();
if (!mSendingHoverAccessibilityEvents) {
if ((action == MotionEvent.ACTION_HOVER_ENTER
|| action == MotionEvent.ACTION_HOVER_MOVE)
&& !hasHoveredChild()
&& pointInView(event.getX(), event.getY())) {
- mSendingHoverAccessibilityEvents = true;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ mSendingHoverAccessibilityEvents = true;
+ requestAccessibilityFocusFromHover((int) event.getX(), (int) event.getY());
}
} else {
if (action == MotionEvent.ACTION_HOVER_EXIT
- || (action == MotionEvent.ACTION_HOVER_MOVE
+ || (action == MotionEvent.ACTION_MOVE
&& !pointInView(event.getX(), event.getY()))) {
mSendingHoverAccessibilityEvents = false;
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ // If the window does not have input focus we take away accessibility
+ // focus as soon as the user stop hovering over the view.
+ if (!mAttachInfo.mHasWindowFocus) {
+ getViewRootImpl().setAccessibilityFocusedHost(null);
+ }
}
}
@@ -6797,6 +7356,7 @@
dispatchGenericMotionEventInternal(event);
return true;
}
+
return false;
}
@@ -6808,7 +7368,6 @@
*/
private boolean isHoverable() {
final int viewFlags = mViewFlags;
- //noinspection SimplifiableIfStatement
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return false;
}
@@ -7132,6 +7691,9 @@
*/
if (mParent != null) mParent.focusableViewAvailable(this);
}
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
if ((flags & VISIBILITY_MASK) == VISIBLE) {
@@ -7163,6 +7725,7 @@
if (((mViewFlags & VISIBILITY_MASK) == GONE)) {
if (hasFocus()) clearFocus();
+ clearAccessibilityFocus();
destroyDrawingCache();
if (mParent instanceof View) {
// GONE views noop invalidation, so invalidate the parent
@@ -7187,9 +7750,10 @@
mPrivateFlags |= DRAWN;
if (((mViewFlags & VISIBILITY_MASK) == INVISIBLE) && hasFocus()) {
- // root view becoming invisible shouldn't clear focus
+ // root view becoming invisible shouldn't clear focus and accessibility focus
if (getRootView() != this) {
clearFocus();
+ clearAccessibilityFocus();
}
}
if (mAttachInfo != null) {
@@ -7243,6 +7807,12 @@
mParent.recomputeViewAttributes(this);
}
}
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && ((changed & FOCUSABLE) != 0 || (changed & CLICKABLE) != 0
+ || (changed & LONG_CLICKABLE) != 0 || (changed & ENABLED) != 0)) {
+ notifyAccessibilityStateChanged();
+ }
}
/**
@@ -7321,6 +7891,7 @@
* @param canvas the canvas on which to draw the view
*/
protected void dispatchDraw(Canvas canvas) {
+
}
/**
@@ -9792,7 +10363,7 @@
* @attr ref android.R.styleable#View_scrollbarSize
*/
public int getScrollBarSize() {
- return mScrollCache == null ? ViewConfiguration.getScrollBarSize() :
+ return mScrollCache == null ? ViewConfiguration.get(mContext).getScaledScrollBarSize() :
mScrollCache.scrollBarSize;
}
@@ -10229,6 +10800,7 @@
resolvePadding();
resolveTextDirection();
resolveTextAlignment();
+ clearAccessibilityFocus();
if (isFocused()) {
InputMethodManager imm = InputMethodManager.peekInstance();
imm.focusIn(this);
@@ -10459,6 +11031,8 @@
resetResolvedLayoutDirection();
resetResolvedTextAlignment();
+ resetAccessibilityStateChanged();
+ clearAccessibilityFocus();
}
/**
@@ -12971,6 +13545,7 @@
* background
*/
public void setBackground(Drawable background) {
+ //noinspection deprecation
setBackgroundDrawable(background);
}
@@ -13274,6 +13849,29 @@
}
/**
+ * @hide
+ */
+ public Insets getLayoutInsets() {
+ if (mLayoutInsets == null) {
+ if (mBackground == null) {
+ mLayoutInsets = Insets.NONE;
+ } else {
+ Rect insetRect = new Rect();
+ boolean hasInsets = mBackground.getLayoutInsets(insetRect);
+ mLayoutInsets = hasInsets ? Insets.of(insetRect) : Insets.NONE;
+ }
+ }
+ return mLayoutInsets;
+ }
+
+ /**
+ * @hide
+ */
+ public void setLayoutInsets(Insets layoutInsets) {
+ mLayoutInsets = layoutInsets;
+ }
+
+ /**
* Changes the selection state of this view. A view can be selected or not.
* Note that selection is not the same as focus. Views are typically
* selected in the context of an AdapterView like ListView or GridView;
@@ -13288,6 +13886,9 @@
invalidate(true);
refreshDrawableState();
dispatchSetSelected(selected);
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ notifyAccessibilityStateChanged();
+ }
}
}
@@ -13457,7 +14058,7 @@
position[1] += view.mTop;
viewParent = view.mParent;
- }
+ }
if (viewParent instanceof ViewRootImpl) {
// *cough*
@@ -14296,7 +14897,15 @@
*/
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
+
if (animation != null) {
+ // If the screen is off assume the animation start time is now instead of
+ // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
+ // would cause the animation to start when the screen turns back on
+ if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
+ animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
+ animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
+ }
animation.reset();
}
}
@@ -16284,7 +16893,7 @@
/**
* Temporary list for use in collecting focusable descendents of a view.
*/
- final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24);
+ final ArrayList<View> mTempArrayList = new ArrayList<View>(24);
/**
* The id of the window for accessibility purposes.
@@ -16292,6 +16901,17 @@
int mAccessibilityWindowId = View.NO_ID;
/**
+ * Whether to ingore not exposed for accessibility Views when
+ * reporting the view tree to accessibility services.
+ */
+ boolean mIncludeNotImportantViews;
+
+ /**
+ * The drawable for highlighting accessibility focus.
+ */
+ Drawable mAccessibilityFocusDrawable;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 9d06145..9134966 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -197,7 +197,7 @@
* gesture and the touch up event of a subsequent tap for the latter tap to be
* considered as a tap i.e. to perform a click.
*/
- private static final int TOUCH_EXPLORATION_TAP_SLOP = 80;
+ private static final int TOUCH_EXPLORE_TAP_SLOP = 80;
/**
* Delay before dispatching a recurring accessibility event in milliseconds.
@@ -238,7 +238,7 @@
private final int mDoubleTapTouchSlop;
private final int mPagingTouchSlop;
private final int mDoubleTapSlop;
- private final int mScaledTouchExplorationTapSlop;
+ private final int mScaledTouchExploreTapSlop;
private final int mWindowTouchSlop;
private final int mMaximumDrawingCacheSize;
private final int mOverscrollDistance;
@@ -265,7 +265,7 @@
mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
mPagingTouchSlop = PAGING_TOUCH_SLOP;
mDoubleTapSlop = DOUBLE_TAP_SLOP;
- mScaledTouchExplorationTapSlop = TOUCH_EXPLORATION_TAP_SLOP;
+ mScaledTouchExploreTapSlop = TOUCH_EXPLORE_TAP_SLOP;
mWindowTouchSlop = WINDOW_TOUCH_SLOP;
//noinspection deprecation
mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
@@ -302,7 +302,7 @@
mMaximumFlingVelocity = (int) (density * MAXIMUM_FLING_VELOCITY + 0.5f);
mScrollbarSize = (int) (density * SCROLL_BAR_SIZE + 0.5f);
mDoubleTapSlop = (int) (sizeAndDensity * DOUBLE_TAP_SLOP + 0.5f);
- mScaledTouchExplorationTapSlop = (int) (density * TOUCH_EXPLORATION_TAP_SLOP + 0.5f);
+ mScaledTouchExploreTapSlop = (int) (density * TOUCH_EXPLORE_TAP_SLOP + 0.5f);
mWindowTouchSlop = (int) (sizeAndDensity * WINDOW_TOUCH_SLOP + 0.5f);
final Display display = WindowManagerImpl.getDefault().getDefaultDisplay();
@@ -559,8 +559,8 @@
*
* @hide
*/
- public int getScaledTouchExplorationTapSlop() {
- return mScaledTouchExplorationTapSlop;
+ public int getScaledTouchExploreTapSlop() {
+ return mScaledTouchExploreTapSlop;
}
/**
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8f6badf..cb37a1c 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -141,6 +141,18 @@
public static final String DEBUG_LATENCY_TAG = "ViewLatency";
/**
+ * Enables detailed logging of accessibility focus operations.
+ * @hide
+ */
+ public static final boolean DEBUG_ACCESSIBILITY_FOCUS = false;
+
+ /**
+ * Tag for logging of accessibility focus operations
+ * @hide
+ */
+ public static final String DEBUG_ACCESSIBILITY_FOCUS_TAG = "AccessibilityFocus";
+
+ /**
* <p>Enables or disables views consistency check. Even when this property is enabled,
* view consistency checks happen only if {@link false} is set
* to true. The value of this property can be configured externally in one of the
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 121b544..5e02b49 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -45,6 +45,7 @@
import com.android.internal.util.Predicate;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
/**
@@ -169,6 +170,12 @@
*/
protected int mGroupFlags;
+ /*
+ * THe layout mode: either {@link #UNDEFINED_LAYOUT_MODE}, {@link #COMPONENT_BOUNDS} or
+ * {@link #LAYOUT_BOUNDS}
+ */
+ private int mLayoutMode = UNDEFINED_LAYOUT_MODE;
+
/**
* NOTE: If you change the flags below make sure to reflect the changes
* the DisplayList class
@@ -334,6 +341,22 @@
*/
public static final int PERSISTENT_ALL_CACHES = 0x3;
+ // Layout Modes
+
+ private static final int UNDEFINED_LAYOUT_MODE = -1;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ * Component bounds are the raw values of {@link #getLeft() left}, {@link #getTop() top},
+ * {@link #getRight() right} and {@link #getBottom() bottom}.
+ */
+ public static final int COMPONENT_BOUNDS = 0;
+
+ /**
+ * This constant is a {@link #setLayoutMode(int) layoutMode}.
+ */
+ public static final int LAYOUT_BOUNDS = 1;
+
/**
* We clip to padding when FLAG_CLIP_TO_PADDING and FLAG_PADDING_NOT_NULL
* are set at the same time.
@@ -611,13 +634,13 @@
/**
* {@inheritDoc}
*/
+ @Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- ViewParent parent = getParent();
+ ViewParent parent = mParent;
if (parent == null) {
return false;
}
final boolean propagate = onRequestSendAccessibilityEvent(child, event);
- //noinspection SimplifiableIfStatement
if (!propagate) {
return false;
}
@@ -1552,6 +1575,33 @@
return mFirstHoverTarget != null;
}
+ @Override
+ public void addChildrenForAccessibility(ArrayList<View> childrenForAccessibility) {
+ View[] children = mChildren;
+ final int childrenCount = mChildrenCount;
+ for (int i = 0; i < childrenCount; i++) {
+ View child = children[i];
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if (child.includeForAccessibility()) {
+ childrenForAccessibility.add(child);
+ } else {
+ child.addChildrenForAccessibility(childrenForAccessibility);
+ }
+ }
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void childAccessibilityStateChanged(View child) {
+ if (mParent != null) {
+ mParent.childAccessibilityStateChanged(child);
+ }
+ }
+
/**
* Implement this method to intercept hover events before they are handled
* by child views.
@@ -2294,33 +2344,43 @@
@Override
boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
- boolean handled = super.dispatchPopulateAccessibilityEventInternal(event);
- if (handled) {
- return handled;
+ boolean handled = false;
+ if (includeForAccessibility()) {
+ handled = super.dispatchPopulateAccessibilityEventInternal(event);
+ if (handled) {
+ return handled;
+ }
}
// Let our children have a shot in populating the event.
- for (int i = 0, count = getChildCount(); i < count; i++) {
- View child = getChildAt(i);
+ ChildListForAccessibility children = ChildListForAccessibility.obtain(this, true);
+ final int childCount = children.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = children.getChildAt(i);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
- handled = getChildAt(i).dispatchPopulateAccessibilityEvent(event);
+ handled = child.dispatchPopulateAccessibilityEvent(event);
if (handled) {
+ children.recycle();
return handled;
}
}
}
+ children.recycle();
return false;
}
@Override
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- info.setClassName(ViewGroup.class.getName());
- for (int i = 0, count = mChildrenCount; i < count; i++) {
- View child = mChildren[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ if (mAttachInfo != null) {
+ ArrayList<View> childrenForAccessibility = mAttachInfo.mTempArrayList;
+ childrenForAccessibility.clear();
+ addChildrenForAccessibility(childrenForAccessibility);
+ final int childrenForAccessibilityCount = childrenForAccessibility.size();
+ for (int i = 0; i < childrenForAccessibilityCount; i++) {
+ View child = childrenForAccessibility.get(i);
info.addChild(child);
}
+ childrenForAccessibility.clear();
}
}
@@ -2331,6 +2391,20 @@
}
/**
+ * @hide
+ */
+ @Override
+ public void resetAccessibilityStateChanged() {
+ super.resetAccessibilityStateChanged();
+ View[] children = mChildren;
+ final int childCount = mChildrenCount;
+ for (int i = 0; i < childCount; i++) {
+ View child = children[i];
+ child.resetAccessibilityStateChanged();
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
@@ -3400,6 +3474,10 @@
clearChildFocus(view);
ensureInputFocusOnFirstFocusable();
}
+
+ if (view.isAccessibilityFocused()) {
+ view.clearAccessibilityFocus();
+ }
}
/**
@@ -4368,6 +4446,52 @@
}
/**
+ * Returns the basis of alignment during the layout of this view group:
+ * either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ *
+ * @return whether or not this view group should use the component or layout bounds during
+ * layout operations
+ *
+ * @see #setLayoutMode(int)
+ */
+ public int getLayoutMode() {
+ if (mLayoutMode == UNDEFINED_LAYOUT_MODE) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) parent;
+ return viewGroup.getLayoutMode();
+ } else {
+ int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+ boolean preJellyBean = targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN;
+ return preJellyBean ? COMPONENT_BOUNDS : LAYOUT_BOUNDS;
+ }
+
+ }
+ return mLayoutMode;
+ }
+
+ /**
+ * Sets the basis of alignment during alignment of this view group.
+ * Valid values are either {@link #COMPONENT_BOUNDS} or {@link #LAYOUT_BOUNDS}.
+ * <p>
+ * The default is to query the property of the parent if this view group has a parent.
+ * If this ViewGroup is the root of the view hierarchy the default
+ * value is {@link #LAYOUT_BOUNDS} for target SDK's greater than JellyBean,
+ * {@link #LAYOUT_BOUNDS} otherwise.
+ *
+ * @return whether or not this view group should use the component or layout bounds during
+ * layout operations
+ *
+ * @see #getLayoutMode()
+ */
+ public void setLayoutMode(int layoutMode) {
+ if (mLayoutMode != layoutMode) {
+ mLayoutMode = layoutMode;
+ requestLayout();
+ }
+ }
+
+ /**
* Returns a new set of layout parameters based on the supplied attributes set.
*
* @param attrs the attributes to build the layout parameters from
@@ -5622,4 +5746,218 @@
}
}
}
+
+ /**
+ * Pooled class that orderes the children of a ViewGroup from start
+ * to end based on how they are laid out and the layout direction.
+ */
+ static class ChildListForAccessibility {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final Object sPoolLock = new Object();
+
+ private static ChildListForAccessibility sPool;
+
+ private static int sPoolSize;
+
+ private boolean mIsPooled;
+
+ private ChildListForAccessibility mNext;
+
+ private final ArrayList<View> mChildren = new ArrayList<View>();
+
+ private final ArrayList<ViewLocationHolder> mHolders = new ArrayList<ViewLocationHolder>();
+
+ public static ChildListForAccessibility obtain(ViewGroup parent, boolean sort) {
+ ChildListForAccessibility list = null;
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ list = sPool;
+ sPool = list.mNext;
+ list.mNext = null;
+ list.mIsPooled = false;
+ sPoolSize--;
+ } else {
+ list = new ChildListForAccessibility();
+ }
+ list.init(parent, sort);
+ return list;
+ }
+ }
+
+ public void recycle() {
+ if (mIsPooled) {
+ throw new IllegalStateException("Instance already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mIsPooled = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+
+ public int getChildCount() {
+ return mChildren.size();
+ }
+
+ public View getChildAt(int index) {
+ return mChildren.get(index);
+ }
+
+ public int getChildIndex(View child) {
+ return mChildren.indexOf(child);
+ }
+
+ private void init(ViewGroup parent, boolean sort) {
+ ArrayList<View> children = mChildren;
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = parent.getChildAt(i);
+ children.add(child);
+ }
+ if (sort) {
+ ArrayList<ViewLocationHolder> holders = mHolders;
+ for (int i = 0; i < childCount; i++) {
+ View child = children.get(i);
+ ViewLocationHolder holder = ViewLocationHolder.obtain(parent, child);
+ holders.add(holder);
+ }
+ Collections.sort(holders);
+ for (int i = 0; i < childCount; i++) {
+ ViewLocationHolder holder = holders.get(i);
+ children.set(i, holder.mView);
+ holder.recycle();
+ }
+ holders.clear();
+ }
+ }
+
+ private void clear() {
+ mChildren.clear();
+ }
+ }
+
+ /**
+ * Pooled class that holds a View and its location with respect to
+ * a specified root. This enables sorting of views based on their
+ * coordinates without recomputing the position relative to the root
+ * on every comparison.
+ */
+ static class ViewLocationHolder implements Comparable<ViewLocationHolder> {
+
+ private static final int MAX_POOL_SIZE = 32;
+
+ private static final Object sPoolLock = new Object();
+
+ private static ViewLocationHolder sPool;
+
+ private static int sPoolSize;
+
+ private boolean mIsPooled;
+
+ private ViewLocationHolder mNext;
+
+ private final Rect mLocation = new Rect();
+
+ public View mView;
+
+ private int mLayoutDirection;
+
+ public static ViewLocationHolder obtain(ViewGroup root, View view) {
+ ViewLocationHolder holder = null;
+ synchronized (sPoolLock) {
+ if (sPool != null) {
+ holder = sPool;
+ sPool = holder.mNext;
+ holder.mNext = null;
+ holder.mIsPooled = false;
+ sPoolSize--;
+ } else {
+ holder = new ViewLocationHolder();
+ }
+ holder.init(root, view);
+ return holder;
+ }
+ }
+
+ public void recycle() {
+ if (mIsPooled) {
+ throw new IllegalStateException("Instance already recycled.");
+ }
+ clear();
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ mIsPooled = true;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+
+ @Override
+ public int compareTo(ViewLocationHolder another) {
+ // This instance is greater than an invalid argument.
+ if (another == null) {
+ return 1;
+ }
+ if (getClass() != another.getClass()) {
+ return 1;
+ }
+ // First is above second.
+ if (mLocation.bottom - another.mLocation.top <= 0) {
+ return -1;
+ }
+ // First is below second.
+ if (mLocation.top - another.mLocation.bottom >= 0) {
+ return 1;
+ }
+ // LTR
+ if (mLayoutDirection == LAYOUT_DIRECTION_LTR) {
+ final int leftDifference = mLocation.left - another.mLocation.left;
+ // First more to the left than second.
+ if (leftDifference != 0) {
+ return leftDifference;
+ }
+ } else { // RTL
+ final int rightDifference = mLocation.right - another.mLocation.right;
+ // First more to the right than second.
+ if (rightDifference != 0) {
+ return -rightDifference;
+ }
+ }
+ // Break tie by top.
+ final int topDiference = mLocation.top - another.mLocation.top;
+ if (topDiference != 0) {
+ return topDiference;
+ }
+ // Break tie by height.
+ final int heightDiference = mLocation.height() - another.mLocation.height();
+ if (heightDiference != 0) {
+ return -heightDiference;
+ }
+ // Break tie by width.
+ final int widthDiference = mLocation.width() - another.mLocation.width();
+ if (widthDiference != 0) {
+ return -widthDiference;
+ }
+ // Return nondeterministically one of them since we do
+ // not want to ignore any views.
+ return 1;
+ }
+
+ private void init(ViewGroup root, View view) {
+ Rect viewLocation = mLocation;
+ view.getDrawingRect(viewLocation);
+ root.offsetDescendantRectToMyCoords(view, viewLocation);
+ mView = view;
+ mLayoutDirection = root.getResolvedLayoutDirection();
+ }
+
+ private void clear() {
+ mView = null;
+ mLocation.set(0, 0, 0, 0);
+ }
+ }
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 75e9151..ddff91d 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -277,4 +277,22 @@
* View.fitSystemWindows(Rect)} be performed.
*/
public void requestFitSystemWindows();
+
+ /**
+ * Gets the parent of a given View for accessibility. Since some Views are not
+ * exposed to the accessibility layer the parent for accessibility is not
+ * necessarily the direct parent of the View, rather it is a predecessor.
+ *
+ * @return The parent or <code>null</code> if no such is found.
+ */
+ public ViewParent getParentForAccessibility();
+
+ /**
+ * A child notifies its parent that its state for accessibility has changed.
+ * That is some of the child properties reported to accessibility services has
+ * changed, hence the interested services have to be notified for the new state.
+ *
+ * @hide
+ */
+ public void childAccessibilityStateChanged(View child);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 899fb32..b4554d5 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -37,6 +37,7 @@
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
+import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Bundle;
@@ -56,17 +57,11 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
-import android.util.Pool;
-import android.util.Poolable;
-import android.util.PoolableManager;
-import android.util.Pools;
import android.util.Slog;
-import android.util.SparseLongArray;
import android.util.TypedValue;
import android.view.View.AttachInfo;
import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -79,6 +74,7 @@
import android.view.inputmethod.InputMethodManager;
import android.widget.Scroller;
+import com.android.internal.R;
import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.IInputMethodCallback;
@@ -89,9 +85,7 @@
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.HashSet;
/**
* The top of a view hierarchy, implementing the needed protocol between View
@@ -181,6 +175,10 @@
View mFocusedView;
View mRealFocusedView; // this is not set to null in touch mode
View mOldFocusedView;
+
+ View mAccessibilityFocusedHost;
+ AccessibilityNodeInfo mAccessibilityFocusedVirtualView;
+
int mViewVisibility;
boolean mAppVisible = true;
int mOrigWindowType = -1;
@@ -321,7 +319,7 @@
SendWindowContentChangedAccessibilityEvent mSendWindowContentChangedAccessibilityEvent;
- AccessibilityNodePrefetcher mAccessibilityNodePrefetcher;
+ HashSet<View> mTempHashSet;
private final int mDensity;
@@ -630,6 +628,10 @@
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
}
+
+ if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
}
}
@@ -1418,6 +1420,8 @@
mView.draw(layerCanvas);
+ drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+
mResizeBufferStartTime = SystemClock.uptimeMillis();
mResizeBufferDuration = mView.getResources().getInteger(
com.android.internal.R.integer.config_mediumAnimTime);
@@ -1712,7 +1716,7 @@
attachInfo.mTreeObserver.dispatchOnGlobalLayout();
if (AccessibilityManager.getInstance(host.mContext).isEnabled()) {
- postSendWindowContentChangedCallback();
+ postSendWindowContentChangedCallback(mView);
}
}
@@ -1880,6 +1884,7 @@
mResizePaint.setAlpha(mResizeAlpha);
canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
}
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
}
/**
@@ -2234,6 +2239,8 @@
mView.draw(canvas);
+ drawAccessibilityFocusedDrawableIfNeeded(canvas);
+
if (ViewDebug.DEBUG_LATENCY) {
long now = System.nanoTime();
Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took "
@@ -2274,6 +2281,64 @@
return true;
}
+ /**
+ * We want to draw a highlight around the current accessibility focused.
+ * Since adding a style for all possible view is not a viable option we
+ * have this specialized drawing method.
+ *
+ * Note: We are doing this here to be able to draw the highlight for
+ * virtual views in addition to real ones.
+ *
+ * @param canvas The canvas on which to draw.
+ */
+ private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
+ if (!AccessibilityManager.getInstance(mView.mContext).isEnabled()) {
+ return;
+ }
+ if (mAccessibilityFocusedHost == null || mAccessibilityFocusedHost.mAttachInfo == null) {
+ return;
+ }
+ Drawable drawable = getAccessibilityFocusedDrawable();
+ if (drawable == null) {
+ return;
+ }
+ AccessibilityNodeProvider provider =
+ mAccessibilityFocusedHost.getAccessibilityNodeProvider();
+ Rect bounds = mView.mAttachInfo.mTmpInvalRect;
+ if (provider == null) {
+ mAccessibilityFocusedHost.getDrawingRect(bounds);
+ if (mView instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) mView;
+ viewGroup.offsetDescendantRectToMyCoords(mAccessibilityFocusedHost, bounds);
+ }
+ } else {
+ if (mAccessibilityFocusedVirtualView == null) {
+ mAccessibilityFocusedVirtualView = provider.findAccessibilitiyFocus(View.NO_ID);
+ }
+ mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
+ bounds.offset(-mAttachInfo.mWindowLeft, -mAttachInfo.mWindowTop);
+ }
+ drawable.setBounds(bounds);
+ drawable.draw(canvas);
+ }
+
+ private Drawable getAccessibilityFocusedDrawable() {
+ if (mAttachInfo != null) {
+ // Lazily load the accessibility focus drawable.
+ if (mAttachInfo.mAccessibilityFocusDrawable == null) {
+ TypedValue value = new TypedValue();
+ final boolean resolved = mView.mContext.getTheme().resolveAttribute(
+ R.attr.accessibilityFocusedDrawable, value, true);
+ if (resolved) {
+ mAttachInfo.mAccessibilityFocusDrawable =
+ mView.mContext.getResources().getDrawable(value.resourceId);
+ }
+ }
+ return mAttachInfo.mAccessibilityFocusDrawable;
+ }
+ return null;
+ }
+
void invalidateDisplayLists() {
final ArrayList<DisplayList> displayLists = mDisplayLists;
final int count = displayLists.size();
@@ -2407,6 +2472,14 @@
return handled;
}
+ void setAccessibilityFocusedHost(View host) {
+ if (mAccessibilityFocusedHost != null && mAccessibilityFocusedVirtualView == null) {
+ mAccessibilityFocusedHost.clearAccessibilityFocusNoCallbacks();
+ }
+ mAccessibilityFocusedHost = host;
+ mAccessibilityFocusedVirtualView = null;
+ }
+
public void requestChildFocus(View child, View focused) {
checkThread();
@@ -2437,9 +2510,13 @@
mFocusedView = mRealFocusedView = null;
}
+ @Override
+ public ViewParent getParentForAccessibility() {
+ return null;
+ }
+
public void focusableViewAvailable(View v) {
checkThread();
-
if (mView != null) {
if (!mView.hasFocus()) {
v.requestFocus();
@@ -2547,7 +2624,7 @@
/**
* Return true if child is an ancestor of parent, (or equal to the parent).
*/
- private static boolean isViewDescendantOf(View child, View parent) {
+ static boolean isViewDescendantOf(View child, View parent) {
if (child == parent) {
return true;
}
@@ -2585,13 +2662,9 @@
private final static int MSG_DISPATCH_DRAG_LOCATION_EVENT = 16;
private final static int MSG_DISPATCH_SYSTEM_UI_VISIBILITY = 17;
private final static int MSG_UPDATE_CONFIGURATION = 18;
- private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 19;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 20;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 21;
- private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22;
- private final static int MSG_PROCESS_INPUT_EVENTS = 23;
- private final static int MSG_DISPATCH_SCREEN_STATE = 24;
- private final static int MSG_INVALIDATE_DISPLAY_LIST = 25;
+ private final static int MSG_PROCESS_INPUT_EVENTS = 19;
+ private final static int MSG_DISPATCH_SCREEN_STATE = 20;
+ private final static int MSG_INVALIDATE_DISPLAY_LIST = 21;
final class ViewRootHandler extends Handler {
@Override
@@ -2633,14 +2706,6 @@
return "MSG_DISPATCH_SYSTEM_UI_VISIBILITY";
case MSG_UPDATE_CONFIGURATION:
return "MSG_UPDATE_CONFIGURATION";
- case MSG_PERFORM_ACCESSIBILITY_ACTION:
- return "MSG_PERFORM_ACCESSIBILITY_ACTION";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT:
- return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT";
case MSG_PROCESS_INPUT_EVENTS:
return "MSG_PROCESS_INPUT_EVENTS";
case MSG_DISPATCH_SCREEN_STATE:
@@ -2770,8 +2835,28 @@
mHasHadWindowFocus = true;
}
- if (hasWindowFocus && mView != null && mAccessibilityManager.isEnabled()) {
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (mView != null && mAccessibilityManager.isEnabled()) {
+ if (hasWindowFocus) {
+ mView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ // Give accessibility focus to the view that has input
+ // focus if such, otherwise to the first one.
+ if (mView instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) mView;
+ View focused = viewGroup.findFocus();
+ if (focused != null) {
+ focused.requestAccessibilityFocus();
+ }
+ }
+ // There is no accessibility focus, despite our effort
+ // above, now just give it to the first view.
+ if (mAccessibilityFocusedHost == null) {
+ mView.requestAccessibilityFocus();
+ }
+ } else {
+ // Clear accessibility focus when the window loses input focus.
+ setAccessibilityFocusedHost(null);
+ }
}
}
} break;
@@ -2828,30 +2913,6 @@
}
updateConfiguration(config, false);
} break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByAccessibilityIdUiThread(msg);
- }
- } break;
- case MSG_PERFORM_ACCESSIBILITY_ACTION: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .perfromAccessibilityActionUiThread(msg);
- }
- } break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfoByViewIdUiThread(msg);
- }
- } break;
- case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: {
- if (mView != null) {
- getAccessibilityInteractionController()
- .findAccessibilityNodeInfosByTextUiThread(msg);
- }
- } break;
case MSG_DISPATCH_SCREEN_STATE: {
if (mView != null) {
handleScreenStateChange(msg.arg1 == 1);
@@ -2917,28 +2978,25 @@
// set yet.
final View focused = mView.findFocus();
if (focused != null && !focused.isFocusableInTouchMode()) {
-
final ViewGroup ancestorToTakeFocus =
findAncestorToTakeFocusInTouchMode(focused);
if (ancestorToTakeFocus != null) {
// there is an ancestor that wants focus after its descendants that
// is focusable in touch mode.. give it focus
return ancestorToTakeFocus.requestFocus();
- } else {
- // nothing appropriate to have focus in touch mode, clear it out
- mView.unFocus();
- mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
- mFocusedView = null;
- mOldFocusedView = null;
- return true;
}
}
+ // nothing appropriate to have focus in touch mode, clear it out
+ mView.unFocus();
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(focused, null);
+ mFocusedView = null;
+ mOldFocusedView = null;
+ return true;
}
}
return false;
}
-
/**
* Find an ancestor of focused that wants focus after its descendants and is
* focusable in touch mode.
@@ -2964,25 +3022,45 @@
private boolean leaveTouchMode() {
if (mView != null) {
+ boolean inputFocusValid = false;
if (mView.hasFocus()) {
// i learned the hard way to not trust mFocusedView :)
mFocusedView = mView.findFocus();
if (!(mFocusedView instanceof ViewGroup)) {
// some view has focus, let it keep it
- return false;
- } else if (((ViewGroup)mFocusedView).getDescendantFocusability() !=
+ inputFocusValid = true;
+ } else if (((ViewGroup) mFocusedView).getDescendantFocusability() !=
ViewGroup.FOCUS_AFTER_DESCENDANTS) {
// some view group has focus, and doesn't prefer its children
// over itself for focus, so let them keep it.
- return false;
+ inputFocusValid = true;
}
}
-
- // find the best view to give focus to in this brave new non-touch-mode
- // world
- final View focused = focusSearch(null, View.FOCUS_DOWN);
- if (focused != null) {
- return focused.requestFocus(View.FOCUS_DOWN);
+ // In accessibility mode we always have a view that has the
+ // accessibility focus and input focus follows it, i.e. we
+ // try to give input focus to the accessibility focused view.
+ if (!AccessibilityManager.getInstance(mView.mContext).isEnabled()) {
+ // If the current input focus is not valid, find the best view to give
+ // focus to in this brave new non-touch-mode world.
+ if (!inputFocusValid) {
+ final View focused = focusSearch(null, View.FOCUS_DOWN);
+ if (focused != null) {
+ return focused.requestFocus(View.FOCUS_DOWN);
+ }
+ }
+ } else {
+ // If the current input focus is not valid clear it but do not
+ // give it to another view since the accessibility focus is
+ // leading now and the input one follows.
+ if (!inputFocusValid) {
+ if (mFocusedView != null) {
+ mView.unFocus();
+ mAttachInfo.mTreeObserver.dispatchOnGlobalFocusChange(mFocusedView, null);
+ mFocusedView = null;
+ mOldFocusedView = null;
+ return true;
+ }
+ }
}
}
return false;
@@ -3487,37 +3565,36 @@
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int direction = 0;
switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_LEFT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_RIGHT;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_UP;
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_DOWN;
- }
- break;
- case KeyEvent.KEYCODE_TAB:
- if (event.hasNoModifiers()) {
- direction = View.FOCUS_FORWARD;
- } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
- direction = View.FOCUS_BACKWARD;
- }
- break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_LEFT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_RIGHT;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_UP;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_DOWN;
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (event.hasNoModifiers()) {
+ direction = View.FOCUS_FORWARD;
+ } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
+ direction = View.FOCUS_BACKWARD;
+ }
+ break;
}
-
if (direction != 0) {
- View focused = mView != null ? mView.findFocus() : null;
+ View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
@@ -3532,8 +3609,8 @@
v, mTempRect);
}
if (v.requestFocus(direction, mTempRect)) {
- playSoundEffect(
- SoundEffectConstants.getContantForFocusDirection(direction));
+ playSoundEffect(SoundEffectConstants
+ .getContantForFocusDirection(direction));
finishInputEvent(q, true);
return;
}
@@ -3683,22 +3760,11 @@
+ " called when there is no mView");
}
if (mAccessibilityInteractionController == null) {
- mAccessibilityInteractionController = new AccessibilityInteractionController();
+ mAccessibilityInteractionController = new AccessibilityInteractionController(this);
}
return mAccessibilityInteractionController;
}
- public AccessibilityNodePrefetcher getAccessibilityNodePrefetcher() {
- if (mView == null) {
- throw new IllegalStateException("getAccessibilityNodePrefetcher"
- + " called when there is no mView");
- }
- if (mAccessibilityNodePrefetcher == null) {
- mAccessibilityNodePrefetcher = new AccessibilityNodePrefetcher();
- }
- return mAccessibilityNodePrefetcher;
- }
-
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
@@ -4375,15 +4441,19 @@
* This event is send at most once every
* {@link ViewConfiguration#getSendRecurringAccessibilityEventsInterval()}.
*/
- private void postSendWindowContentChangedCallback() {
+ private void postSendWindowContentChangedCallback(View source) {
if (mSendWindowContentChangedAccessibilityEvent == null) {
mSendWindowContentChangedAccessibilityEvent =
new SendWindowContentChangedAccessibilityEvent();
}
- if (!mSendWindowContentChangedAccessibilityEvent.mIsPending) {
- mSendWindowContentChangedAccessibilityEvent.mIsPending = true;
+ View oldSource = mSendWindowContentChangedAccessibilityEvent.mSource;
+ if (oldSource == null) {
+ mSendWindowContentChangedAccessibilityEvent.mSource = source;
mHandler.postDelayed(mSendWindowContentChangedAccessibilityEvent,
ViewConfiguration.getSendRecurringAccessibilityEventsInterval());
+ } else {
+ View newSource = getCommonPredecessor(oldSource, source);
+ mSendWindowContentChangedAccessibilityEvent.mSource = newSource;
}
}
@@ -4419,6 +4489,46 @@
return true;
}
+ @Override
+ public void childAccessibilityStateChanged(View child) {
+ postSendWindowContentChangedCallback(child);
+ }
+
+ private View getCommonPredecessor(View first, View second) {
+ if (mAttachInfo != null) {
+ if (mTempHashSet == null) {
+ mTempHashSet = new HashSet<View>();
+ }
+ HashSet<View> seen = mTempHashSet;
+ seen.clear();
+ View firstCurrent = first;
+ while (firstCurrent != null) {
+ seen.add(firstCurrent);
+ ViewParent firstCurrentParent = firstCurrent.mParent;
+ if (firstCurrentParent instanceof View) {
+ firstCurrent = (View) firstCurrentParent;
+ } else {
+ firstCurrent = null;
+ }
+ }
+ View secondCurrent = second;
+ while (secondCurrent != null) {
+ if (seen.contains(secondCurrent)) {
+ seen.clear();
+ return secondCurrent;
+ }
+ ViewParent secondCurrentParent = secondCurrent.mParent;
+ if (secondCurrentParent instanceof View) {
+ secondCurrent = (View) secondCurrentParent;
+ } else {
+ secondCurrent = null;
+ }
+ }
+ seen.clear();
+ }
+ return null;
+ }
+
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
@@ -4953,6 +5063,7 @@
}
} else {
ensureNoConnection();
+ setAccessibilityFocusedHost(null);
}
}
@@ -4991,14 +5102,15 @@
mViewRootImpl = new WeakReference<ViewRootImpl>(viewRootImpl);
}
+ @Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int prefetchFlags, int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
- interactionId, callback, prefetchFlags, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
@@ -5009,16 +5121,17 @@
}
}
+ @Override
public void performAccessibilityAction(long accessibilityNodeId, int action,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interogatingPid, long interrogatingTid) {
+ int flags, int interogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action,
- interactionId, callback, interogatingPid, interrogatingTid);
+ interactionId, callback, flags, interogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setPerformAccessibilityActionResult(false, interactionId);
} catch (RemoteException re) {
@@ -5027,16 +5140,17 @@
}
}
+ @Override
public void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int viewId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByViewIdClientThread(accessibilityNodeId, viewId,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
@@ -5045,16 +5159,17 @@
}
}
+ @Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid) {
+ int flags, int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
- // We cannot make the call and notify the caller so it does not
+ // We cannot make the call and notify the caller so it does not wait.
try {
callback.setFindAccessibilityNodeInfosResult(null, interactionId);
} catch (RemoteException re) {
@@ -5062,593 +5177,54 @@
}
}
}
- }
- /**
- * Class for managing accessibility interactions initiated from the system
- * and targeting the view hierarchy. A *ClientThread method is to be
- * called from the interaction connection this ViewAncestor gives the
- * system to talk to it and a corresponding *UiThread method that is executed
- * on the UI thread.
- */
- final class AccessibilityInteractionController {
- private static final int POOL_SIZE = 5;
-
- private ArrayList<AccessibilityNodeInfo> mTempAccessibilityNodeInfoList =
- new ArrayList<AccessibilityNodeInfo>();
-
- // Reusable poolable arguments for interacting with the view hierarchy
- // to fit more arguments than Message and to avoid sharing objects between
- // two messages since several threads can send messages concurrently.
- private final Pool<SomeArgs> mPool = Pools.synchronizedPool(Pools.finitePool(
- new PoolableManager<SomeArgs>() {
- public SomeArgs newInstance() {
- return new SomeArgs();
- }
-
- public void onAcquired(SomeArgs info) {
- /* do nothing */
- }
-
- public void onReleased(SomeArgs info) {
- info.clear();
- }
- }, POOL_SIZE)
- );
-
- public class SomeArgs implements Poolable<SomeArgs> {
- private SomeArgs mNext;
- private boolean mIsPooled;
-
- public Object arg1;
- public Object arg2;
- public int argi1;
- public int argi2;
- public int argi3;
-
- public SomeArgs getNextPoolable() {
- return mNext;
- }
-
- public boolean isPooled() {
- return mIsPooled;
- }
-
- public void setNextPoolable(SomeArgs args) {
- mNext = args;
- }
-
- public void setPooled(boolean isPooled) {
- mIsPooled = isPooled;
- }
-
- private void clear() {
- arg1 = null;
- arg2 = null;
- argi1 = 0;
- argi2 = 0;
- argi3 = 0;
- }
- }
-
- public void findAccessibilityNodeInfoByAccessibilityIdClientThread(
- long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
+ @Override
+ public void findFocus(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID;
- message.arg1 = prefetchFlags;
- SomeArgs args = mPool.acquire();
- args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- args.argi3 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .findFocusClientThread(accessibilityNodeId, interactionId, focusType,
+ callback, flags, interrogatingPid, interrogatingTid);
} else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
- final int prefetchFlags = message.arg1;
- SomeArgs args = (SomeArgs) message.obj;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
- final int interactionId = args.argi3;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
- infos.clear();
- try {
- View target = null;
- if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED) {
- target = ViewRootImpl.this.mView;
- } else {
- target = findViewByAccessibilityId(accessibilityViewId);
- }
- if (target != null && target.getVisibility() == View.VISIBLE) {
- getAccessibilityNodePrefetcher().prefetchAccessibilityNodeInfos(target,
- virtualDescendantId, prefetchFlags, infos);
- }
- } finally {
+ // We cannot make the call and notify the caller so it does not wait.
try {
- callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
- infos.clear();
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
- /* ignore - the other side will time out */
+ /* best effort - ignore */
}
}
}
- public void findAccessibilityNodeInfoByViewIdClientThread(long accessibilityNodeId,
- int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback,
+ @Override
+ public void focusSearch(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID;
- message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
- args.argi1 = viewId;
- args.argi2 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
+ ViewRootImpl viewRootImpl = mViewRootImpl.get();
+ if (viewRootImpl != null && viewRootImpl.mView != null) {
+ viewRootImpl.getAccessibilityInteractionController()
+ .focusSearchClientThread(accessibilityNodeId, interactionId, direction,
+ callback, flags, interrogatingPid, interrogatingTid);
} else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfoByViewIdUiThread(Message message) {
- final int accessibilityViewId = message.arg1;
- SomeArgs args = (SomeArgs) message.obj;
- final int viewId = args.argi1;
- final int interactionId = args.argi2;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- AccessibilityNodeInfo info = null;
- try {
- View root = null;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- root = findViewByAccessibilityId(accessibilityViewId);
- } else {
- root = ViewRootImpl.this.mView;
- }
- if (root != null) {
- View target = root.findViewById(viewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
- info = target.createAccessibilityNodeInfo();
- }
- }
- } finally {
+ // We cannot make the call and notify the caller so it does not wait.
try {
- callback.setFindAccessibilityNodeInfoResult(info, interactionId);
+ callback.setFindAccessibilityNodeInfoResult(null, interactionId);
} catch (RemoteException re) {
- /* ignore - the other side will time out */
+ /* best effort - ignore */
}
}
}
-
- public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,
- String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
- long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT;
- SomeArgs args = mPool.acquire();
- args.arg1 = text;
- args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- args.argi3 = interactionId;
- args.arg2 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interrogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
- }
- }
-
- public void findAccessibilityNodeInfosByTextUiThread(Message message) {
- SomeArgs args = (SomeArgs) message.obj;
- final String text = (String) args.arg1;
- final int accessibilityViewId = args.argi1;
- final int virtualDescendantId = args.argi2;
- final int interactionId = args.argi3;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg2;
- mPool.release(args);
- List<AccessibilityNodeInfo> infos = null;
- try {
- View target;
- if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- target = findViewByAccessibilityId(accessibilityViewId);
- } else {
- target = ViewRootImpl.this.mView;
- }
- if (target != null && target.getVisibility() == View.VISIBLE) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- infos = provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
- ArrayList<View> foundViews = mAttachInfo.mFocusablesTempList;
- foundViews.clear();
- target.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT
- | View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION
- | View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
- if (!foundViews.isEmpty()) {
- infos = mTempAccessibilityNodeInfoList;
- infos.clear();
- final int viewCount = foundViews.size();
- for (int i = 0; i < viewCount; i++) {
- View foundView = foundViews.get(i);
- if (foundView.getVisibility() == View.VISIBLE) {
- provider = foundView.getAccessibilityNodeProvider();
- if (provider != null) {
- List<AccessibilityNodeInfo> infosFromProvider =
- provider.findAccessibilityNodeInfosByText(text,
- virtualDescendantId);
- if (infosFromProvider != null) {
- infos.addAll(infosFromProvider);
- }
- } else {
- infos.add(foundView.createAccessibilityNodeInfo());
- }
- }
- }
- }
- }
- }
- } finally {
- try {
- callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
- }
-
- public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
- int interactionId, IAccessibilityInteractionConnectionCallback callback,
- int interogatingPid, long interrogatingTid) {
- Message message = mHandler.obtainMessage();
- message.what = MSG_PERFORM_ACCESSIBILITY_ACTION;
- message.arg1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
- message.arg2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
- SomeArgs args = mPool.acquire();
- args.argi1 = action;
- args.argi2 = interactionId;
- args.arg1 = callback;
- message.obj = args;
- // If the interrogation is performed by the same thread as the main UI
- // thread in this process, set the message as a static reference so
- // after this call completes the same thread but in the interrogating
- // client can handle the message to generate the result.
- if (interogatingPid == Process.myPid()
- && interrogatingTid == Looper.getMainLooper().getThread().getId()) {
- AccessibilityInteractionClient.getInstanceForThread(
- interrogatingTid).setSameThreadMessage(message);
- } else {
- mHandler.sendMessage(message);
- }
- }
-
- public void perfromAccessibilityActionUiThread(Message message) {
- final int accessibilityViewId = message.arg1;
- final int virtualDescendantId = message.arg2;
- SomeArgs args = (SomeArgs) message.obj;
- final int action = args.argi1;
- final int interactionId = args.argi2;
- final IAccessibilityInteractionConnectionCallback callback =
- (IAccessibilityInteractionConnectionCallback) args.arg1;
- mPool.release(args);
- boolean succeeded = false;
- try {
- View target = findViewByAccessibilityId(accessibilityViewId);
- if (target != null && target.getVisibility() == View.VISIBLE) {
- AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
- if (provider != null) {
- succeeded = provider.performAccessibilityAction(action,
- virtualDescendantId);
- } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED) {
- switch (action) {
- case AccessibilityNodeInfo.ACTION_FOCUS: {
- if (!target.hasFocus()) {
- // Get out of touch mode since accessibility
- // wants to move focus around.
- ensureTouchMode(false);
- succeeded = target.requestFocus();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {
- if (target.hasFocus()) {
- target.clearFocus();
- succeeded = !target.isFocused();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_SELECT: {
- if (!target.isSelected()) {
- target.setSelected(true);
- succeeded = target.isSelected();
- }
- } break;
- case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {
- if (target.isSelected()) {
- target.setSelected(false);
- succeeded = !target.isSelected();
- }
- } break;
- }
- }
- }
- } finally {
- try {
- callback.setPerformAccessibilityActionResult(succeeded, interactionId);
- } catch (RemoteException re) {
- /* ignore - the other side will time out */
- }
- }
- }
-
- private View findViewByAccessibilityId(int accessibilityId) {
- View root = ViewRootImpl.this.mView;
- if (root == null) {
- return null;
- }
- View foundView = root.findViewByAccessibilityId(accessibilityId);
- if (foundView != null && foundView.getVisibility() != View.VISIBLE) {
- return null;
- }
- return foundView;
- }
}
private class SendWindowContentChangedAccessibilityEvent implements Runnable {
- public volatile boolean mIsPending;
+ public View mSource;
public void run() {
- if (mView != null) {
- mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- mIsPending = false;
- }
- }
- }
-
- /**
- * This class encapsulates a prefetching strategy for the accessibility APIs for
- * querying window content. It is responsible to prefetch a batch of
- * AccessibilityNodeInfos in addition to the one for a requested node.
- */
- class AccessibilityNodePrefetcher {
-
- private static final int MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE = 50;
-
- public void prefetchAccessibilityNodeInfos(View view, int virtualViewId, int prefetchFlags,
- List<AccessibilityNodeInfo> outInfos) {
- AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
- if (provider == null) {
- AccessibilityNodeInfo root = view.createAccessibilityNodeInfo();
- if (root != null) {
- outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfRealNode(view, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfRealNode(view, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfRealNode(view, outInfos);
- }
- }
- } else {
- AccessibilityNodeInfo root = provider.createAccessibilityNodeInfo(virtualViewId);
- if (root != null) {
- outInfos.add(root);
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS) != 0) {
- prefetchPredecessorsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_SIBLINGS) != 0) {
- prefetchSiblingsOfVirtualNode(root, view, provider, outInfos);
- }
- if ((prefetchFlags & AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS) != 0) {
- prefetchDescendantsOfVirtualNode(root, provider, outInfos);
- }
- }
- }
- }
-
- private void prefetchPredecessorsOfRealNode(View view,
- List<AccessibilityNodeInfo> outInfos) {
- ViewParent parent = view.getParent();
- while (parent instanceof View
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- View parentView = (View) parent;
- final long parentNodeId = AccessibilityNodeInfo.makeNodeId(
- parentView.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeInfo info = parentView.createAccessibilityNodeInfo();
- if (info != null) {
- outInfos.add(info);
- }
- parent = parent.getParent();
- }
- }
-
- private void prefetchSiblingsOfRealNode(View current,
- List<AccessibilityNodeInfo> outInfos) {
- ViewParent parent = current.getParent();
- if (parent instanceof ViewGroup) {
- ViewGroup parentGroup = (ViewGroup) parent;
- final int childCount = parentGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = parentGroup.getChildAt(i);
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE
- && child.getAccessibilityViewId() != current.getAccessibilityViewId()
- && child.getVisibility() == View.VISIBLE) {
- final long childNodeId = AccessibilityNodeInfo.makeNodeId(
- child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeInfo info = null;
- AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
- if (provider == null) {
- info = child.createAccessibilityNodeInfo();
- } else {
- info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
- }
- if (info != null) {
- outInfos.add(info);
- }
- }
- }
- }
- }
-
- private void prefetchDescendantsOfRealNode(View root,
- List<AccessibilityNodeInfo> outInfos) {
- if (root instanceof ViewGroup) {
- ViewGroup rootGroup = (ViewGroup) root;
- HashMap<View, AccessibilityNodeInfo> addedChildren =
- new HashMap<View, AccessibilityNodeInfo>();
- final int childCount = rootGroup.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = rootGroup.getChildAt(i);
- if (child.getVisibility() == View.VISIBLE
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final long childNodeId = AccessibilityNodeInfo.makeNodeId(
- child.getAccessibilityViewId(), AccessibilityNodeInfo.UNDEFINED);
- AccessibilityNodeProvider provider = child.getAccessibilityNodeProvider();
- if (provider == null) {
- AccessibilityNodeInfo info = child.createAccessibilityNodeInfo();
- if (info != null) {
- outInfos.add(info);
- addedChildren.put(child, null);
- }
- } else {
- AccessibilityNodeInfo info = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.UNDEFINED);
- if (info != null) {
- outInfos.add(info);
- addedChildren.put(child, info);
- }
- }
- }
- }
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- for (Map.Entry<View, AccessibilityNodeInfo> entry : addedChildren.entrySet()) {
- View addedChild = entry.getKey();
- AccessibilityNodeInfo virtualRoot = entry.getValue();
- if (virtualRoot == null) {
- prefetchDescendantsOfRealNode(addedChild, outInfos);
- } else {
- AccessibilityNodeProvider provider =
- addedChild.getAccessibilityNodeProvider();
- prefetchDescendantsOfVirtualNode(virtualRoot, provider, outInfos);
- }
- }
- }
- }
- }
-
- private void prefetchPredecessorsOfVirtualNode(AccessibilityNodeInfo root,
- View providerHost, AccessibilityNodeProvider provider,
- List<AccessibilityNodeInfo> outInfos) {
- long parentNodeId = root.getParentNodeId();
- int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
- while (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED) {
- final int virtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED
- || accessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent = provider.createAccessibilityNodeInfo(
- virtualDescendantId);
- if (parent != null) {
- outInfos.add(parent);
- }
- parentNodeId = parent.getParentNodeId();
- accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(
- parentNodeId);
- } else {
- prefetchPredecessorsOfRealNode(providerHost, outInfos);
- return;
- }
- }
- }
-
- private void prefetchSiblingsOfVirtualNode(AccessibilityNodeInfo current, View providerHost,
- AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
- final long parentNodeId = current.getParentNodeId();
- final int parentAccessibilityViewId =
- AccessibilityNodeInfo.getAccessibilityViewId(parentNodeId);
- final int parentVirtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(parentNodeId);
- if (parentVirtualDescendantId != AccessibilityNodeInfo.UNDEFINED
- || parentAccessibilityViewId == providerHost.getAccessibilityViewId()) {
- AccessibilityNodeInfo parent =
- provider.createAccessibilityNodeInfo(parentVirtualDescendantId);
- if (parent != null) {
- SparseLongArray childNodeIds = parent.getChildNodeIds();
- final int childCount = childNodeIds.size();
- for (int i = 0; i < childCount; i++) {
- final long childNodeId = childNodeIds.get(i);
- if (childNodeId != current.getSourceNodeId()
- && outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final int childVirtualDescendantId =
- AccessibilityNodeInfo.getVirtualDescendantId(childNodeId);
- AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
- childVirtualDescendantId);
- if (child != null) {
- outInfos.add(child);
- }
- }
- }
- }
- } else {
- prefetchSiblingsOfRealNode(providerHost, outInfos);
- }
- }
-
- private void prefetchDescendantsOfVirtualNode(AccessibilityNodeInfo root,
- AccessibilityNodeProvider provider, List<AccessibilityNodeInfo> outInfos) {
- SparseLongArray childNodeIds = root.getChildNodeIds();
- final int initialOutInfosSize = outInfos.size();
- final int childCount = childNodeIds.size();
- for (int i = 0; i < childCount; i++) {
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final long childNodeId = childNodeIds.get(i);
- AccessibilityNodeInfo child = provider.createAccessibilityNodeInfo(
- AccessibilityNodeInfo.getVirtualDescendantId(childNodeId));
- if (child != null) {
- outInfos.add(child);
- }
- }
- }
- if (outInfos.size() < MAX_ACCESSIBILITY_NODE_INFO_BATCH_SIZE) {
- final int addedChildCount = outInfos.size() - initialOutInfosSize;
- for (int i = 0; i < addedChildCount; i++) {
- AccessibilityNodeInfo child = outInfos.get(initialOutInfosSize + i);
- prefetchDescendantsOfVirtualNode(child, provider, outInfos);
- }
+ if (mSource != null) {
+ mSource.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ mSource.resetAccessibilityStateChanged();
+ mSource = null;
}
}
}
diff --git a/core/java/android/view/VolumePanel.java b/core/java/android/view/VolumePanel.java
index 9152cc3..110c239 100644
--- a/core/java/android/view/VolumePanel.java
+++ b/core/java/android/view/VolumePanel.java
@@ -263,7 +263,7 @@
| LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
- mVibrator = new Vibrator();
+ mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable);
mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume;
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 66bdc5d..27baaea 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -667,7 +667,7 @@
/**
* Create and return an animation to re-display a force hidden window.
*/
- public Animation createForceHideEnterAnimation();
+ public Animation createForceHideEnterAnimation(boolean onWallpaper);
/**
* Called from the input reader thread before a key is enqueued.
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 0998c80..6cb1578 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -508,7 +508,7 @@
public static final int TYPE_VIEW_SELECTED = 0x00000004;
/**
- * Represents the event of focusing a {@link android.view.View}.
+ * Represents the event of setting input focus of a {@link android.view.View}.
*/
public static final int TYPE_VIEW_FOCUSED = 0x00000008;
@@ -549,7 +549,8 @@
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 0x00000400;
/**
- * Represents the event of changing the content of a window.
+ * Represents the event of changing the content of a window and more
+ * specifically the sub-tree rooted at the event's source.
*/
public static final int TYPE_WINDOW_CONTENT_CHANGED = 0x00000800;
@@ -569,6 +570,16 @@
public static final int TYPE_ANNOUNCEMENT = 0x00004000;
/**
+ * Represents the event of gaining accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 0x00008000;
+
+ /**
+ * Represents the event of clearing accessibility focus.
+ */
+ public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 0x00010000;
+
+ /**
* Mask for {@link AccessibilityEvent} all types.
*
* @see #TYPE_VIEW_CLICKED
@@ -1018,6 +1029,10 @@
return "TYPE_VIEW_SCROLLED";
case TYPE_ANNOUNCEMENT:
return "TYPE_ANNOUNCEMENT";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUSED";
+ case TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED:
+ return "TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED";
default:
return null;
}
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index be74b31..35f0d9d 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -18,7 +18,9 @@
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Message;
+import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
@@ -174,7 +176,7 @@
final int interactionId = mInteractionIdCounter.getAndIncrement();
final float windowScale = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
- Thread.currentThread().getId(), prefetchFlags);
+ prefetchFlags, Thread.currentThread().getId());
// If the scale is zero the call has failed.
if (windowScale > 0) {
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
@@ -293,6 +295,96 @@
}
/**
+ * Finds the {@link android.view.accessibility.AccessibilityNodeInfo} that has the
+ * specified focus type. The search is performed in the window whose id is specified
+ * and starts from the node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param focusType The focus type.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo findFocus(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int focusType) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final float windowScale = connection.findFocus(accessibilityWindowId,
+ accessibilityNodeId, focusType, interactionId, this,
+ Thread.currentThread().getId());
+ // If the scale is zero the call has failed.
+ if (windowScale > 0) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "Error while calling remote findAccessibilityFocus", re);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds the accessibility focused {@link android.view.accessibility.AccessibilityNodeInfo}.
+ * The search is performed in the window whose id is specified and starts from the
+ * node whose accessibility id is specified.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param accessibilityWindowId A unique window id. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID}
+ * to query the currently active window.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param direction The direction in which to search for focusable.
+ * @return The accessibility focused {@link AccessibilityNodeInfo}.
+ */
+ public AccessibilityNodeInfo focusSearch(int connectionId, int accessibilityWindowId,
+ long accessibilityNodeId, int direction) {
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ final int interactionId = mInteractionIdCounter.getAndIncrement();
+ final float windowScale = connection.focusSearch(accessibilityWindowId,
+ accessibilityNodeId, direction, interactionId, this,
+ Thread.currentThread().getId());
+ // If the scale is zero the call has failed.
+ if (windowScale > 0) {
+ AccessibilityNodeInfo info = getFindAccessibilityNodeInfoResultAndClear(
+ interactionId);
+ finalizeAndCacheAccessibilityNodeInfo(info, connectionId, windowScale);
+ return info;
+ }
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "Error while calling remote accessibilityFocusSearch", re);
+ }
+ }
+ return null;
+ }
+
+ /**
* Performs an accessibility action on an {@link AccessibilityNodeInfo}.
*
* @param connectionId The id of a connection for interacting with the system.
@@ -382,7 +474,12 @@
int interactionId) {
synchronized (mInstanceLock) {
final boolean success = waitForResultTimedLocked(interactionId);
- List<AccessibilityNodeInfo> result = success ? mFindAccessibilityNodeInfosResult : null;
+ List<AccessibilityNodeInfo> result = null;
+ if (success) {
+ result = mFindAccessibilityNodeInfosResult;
+ } else {
+ result = Collections.emptyList();
+ }
clearResultLocked();
return result;
}
@@ -395,13 +492,18 @@
int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
- // If the call is not an IPC, i.e. it is made from the same process, we need to
- // instantiate new result list to avoid passing internal instances to clients.
- final boolean isIpcCall = (queryLocalInterface(getInterfaceDescriptor()) == null);
- if (!isIpcCall) {
- mFindAccessibilityNodeInfosResult = new ArrayList<AccessibilityNodeInfo>(infos);
+ if (infos != null) {
+ // If the call is not an IPC, i.e. it is made from the same process, we need to
+ // instantiate new result list to avoid passing internal instances to clients.
+ final boolean isIpcCall = (Binder.getCallingPid() != Process.myPid());
+ if (!isIpcCall) {
+ mFindAccessibilityNodeInfosResult =
+ new ArrayList<AccessibilityNodeInfo>(infos);
+ } else {
+ mFindAccessibilityNodeInfosResult = infos;
+ }
} else {
- mFindAccessibilityNodeInfosResult = infos;
+ mFindAccessibilityNodeInfosResult = Collections.emptyList();
}
mInteractionId = interactionId;
}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index e37de6f..77fd12a 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -204,6 +204,12 @@
* @param event The event to send.
*
* @throws IllegalStateException if accessibility is not enabled.
+ *
+ * <strong>Note:</strong> The preferred mechanism for sending custom accessibility
+ * events is through calling
+ * {@link android.view.ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
+ * instead of this method to allow predecessors to augment/filter events sent by
+ * their descendants.
*/
public void sendAccessibilityEvent(AccessibilityEvent event) {
if (!mIsEnabled) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f616dca..1071c65 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -74,29 +74,57 @@
public static final int FLAG_PREFETCH_SIBLINGS = 0x00000002;
/** @hide */
- public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000003;
+ public static final int FLAG_PREFETCH_DESCENDANTS = 0x00000004;
+
+ /** @hide */
+ public static final int INCLUDE_NOT_IMPORTANT_VIEWS = 0x00000008;
// Actions.
/**
- * Action that focuses the node.
+ * Action that gives input focus to the node.
*/
- public static final int ACTION_FOCUS = 0x00000001;
+ public static final int ACTION_FOCUS = 0x00000001;
/**
- * Action that unfocuses the node.
+ * Action that clears input focus of the node.
*/
- public static final int ACTION_CLEAR_FOCUS = 0x00000002;
+ public static final int ACTION_CLEAR_FOCUS = 0x00000002;
/**
* Action that selects the node.
*/
- public static final int ACTION_SELECT = 0x00000004;
+ public static final int ACTION_SELECT = 0x00000004;
/**
* Action that unselects the node.
*/
- public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+ public static final int ACTION_CLEAR_SELECTION = 0x00000008;
+
+ /**
+ * Action that gives accessibility focus to the node.
+ */
+ public static final int ACTION_ACCESSIBILITY_FOCUS = 0x00000010;
+
+ /**
+ * Action that clears accessibility focus of the node.
+ */
+ public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 0x00000020;
+
+ /**
+ * Action that clicks on the node info./AccessibilityNodeInfoCache.java
+ */
+ public static final int ACTION_CLICK = 0x00000040;
+
+ /**
+ * The input focus.
+ */
+ public static final int FOCUS_INPUT = 1;
+
+ /**
+ * The accessibility focus.
+ */
+ public static final int FOCUS_ACCESSIBILITY = 2;
// Boolean attributes.
@@ -120,6 +148,8 @@
private static final int PROPERTY_SCROLLABLE = 0x00000200;
+ private static final int PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
+
/**
* Bits that provide the id of a virtual descendant of a view.
*/
@@ -248,6 +278,57 @@
(root != null) ? root.getAccessibilityViewId() : UNDEFINED;
mSourceNodeId = makeNodeId(rootAccessibilityViewId, virtualDescendantId);
}
+
+ /**
+ * Find the view that has the input focus. The search starts from
+ * the view represented by this node info.
+ *
+ * @param focus The focus to find. One of {@link #FOCUS_INPUT} or
+ * {@link #FOCUS_ACCESSIBILITY}.
+ * @return The node info of the focused view or null.
+ *
+ * @see #FOCUS_INPUT
+ * @see #FOCUS_ACCESSIBILITY
+ */
+ public AccessibilityNodeInfo findFocus(int focus) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().findFocus(mConnectionId, mWindowId,
+ mSourceNodeId, focus);
+ }
+
+ /**
+ * Searches for the nearest view in the specified direction that can take
+ * the input focus.
+ *
+ * @param direction The direction. Can be one of:
+ * {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_UP},
+ * {@link View#FOCUS_LEFT},
+ * {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_IN},
+ * {@link View#ACCESSIBILITY_FOCUS_OUT},
+ * {@link View#ACCESSIBILITY_FOCUS_FORWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_UP},
+ * {@link View#ACCESSIBILITY_FOCUS_RIGHT},
+ * {@link View#ACCESSIBILITY_FOCUS_DOWN},
+ * {@link View#ACCESSIBILITY_FOCUS_LEFT}.
+ *
+ * @return The node info for the view that can take accessibility focus.
+ */
+ public AccessibilityNodeInfo focusSearch(int direction) {
+ enforceSealed();
+ if (!canPerformRequestOverConnection(mSourceNodeId)) {
+ return null;
+ }
+ return AccessibilityInteractionClient.getInstance().focusSearch(mConnectionId, mWindowId,
+ mSourceNodeId, direction);
+ }
/**
* Gets the id of the window from which the info comes from.
@@ -642,6 +723,31 @@
}
/**
+ * Gets whether this node is accessibility focused.
+ *
+ * @return True if the node is accessibility focused.
+ */
+ public boolean isAccessibilityFocused() {
+ return getBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED);
+ }
+
+ /**
+ * Sets whether this node is accessibility focused.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param focused True if the node is accessibility focused.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setAccessibilityFocused(boolean focused) {
+ setBooleanProperty(PROPERTY_ACCESSIBILITY_FOCUSED, focused);
+ }
+
+ /**
* Gets whether this node is selected.
*
* @return True if the node is selected.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
index dfbfc70..d2609bb 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfoCache.java
@@ -18,6 +18,7 @@
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.SparseLongArray;
/**
* Simple cache for AccessibilityNodeInfos. The cache is mapping an
@@ -54,20 +55,25 @@
* @param event An event.
*/
public void onAccessibilityEvent(AccessibilityEvent event) {
- final int eventType = event.getEventType();
- switch (eventType) {
- case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
- case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_SCROLLED:
- clear();
- break;
- case AccessibilityEvent.TYPE_VIEW_FOCUSED:
- case AccessibilityEvent.TYPE_VIEW_SELECTED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
- case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
- final long accessibilityNodeId = event.getSourceNodeId();
- remove(accessibilityNodeId);
- break;
+ if (ENABLED) {
+ final int eventType = event.getEventType();
+ switch (eventType) {
+ case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
+ clear();
+ } break;
+ case AccessibilityEvent.TYPE_VIEW_FOCUSED:
+ case AccessibilityEvent.TYPE_VIEW_SELECTED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: {
+ final long accessibilityNodeId = event.getSourceNodeId();
+ remove(accessibilityNodeId);
+ } break;
+ case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
+ case AccessibilityEvent.TYPE_VIEW_SCROLLED: {
+ final long accessibilityNodeId = event.getSourceNodeId();
+ clearSubTree(accessibilityNodeId);
+ } break;
+ }
}
}
@@ -167,4 +173,23 @@
}
}
}
+
+ /**
+ * Clears a subtree rooted at the node with the given id.
+ *
+ * @param rootNodeId The root id.
+ */
+ private void clearSubTree(long rootNodeId) {
+ AccessibilityNodeInfo current = mCacheImpl.get(rootNodeId);
+ if (current == null) {
+ return;
+ }
+ mCacheImpl.remove(rootNodeId);
+ SparseLongArray childNodeIds = current.getChildNodeIds();
+ final int childCount = childNodeIds.size();
+ for (int i = 0; i < childCount; i++) {
+ final long childNodeId = childNodeIds.valueAt(i);
+ clearSubTree(childNodeId);
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeProvider.java b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
index 5890417..19e35dd 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeProvider.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeProvider.java
@@ -87,6 +87,7 @@
* @return A populated {@link AccessibilityNodeInfo} for a virtual descendant or the
* host View.
*
+ * @see View#createAccessibilityNodeInfo()
* @see AccessibilityNodeInfo
*/
public AccessibilityNodeInfo createAccessibilityNodeInfo(int virtualViewId) {
@@ -102,6 +103,7 @@
* @param virtualViewId A client defined virtual view id.
* @return True if the action was performed.
*
+ * @see View#performAccessibilityAction(int)
* @see #createAccessibilityNodeInfo(int)
* @see AccessibilityNodeInfo
*/
@@ -127,4 +129,58 @@
int virtualViewId) {
return null;
}
+
+ /**
+ * Finds the accessibility focused {@link AccessibilityNodeInfo}. The search is
+ * relative to the virtual view, i.e. a descendant of the host View, with the
+ * given <code>virtualViewId</code> or the host View itself
+ * <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ *
+ * <strong>Note:</strong> Normally the system is responsible to transparently find
+ * accessibility focused view starting from a given root but for virtual view
+ * hierarchies it is a responsibility of this provider's implementor to find
+ * the accessibility focused virtual view.
+ *
+ * @param virtualViewId A client defined virtual view id which defined
+ * the root of the tree in which to perform the search.
+ * @return A list of node info.
+ *
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo findAccessibilitiyFocus(int virtualViewId) {
+ return null;
+ }
+
+ /**
+ * Finds {@link AccessibilityNodeInfo} to take accessibility focus in the given
+ * <code>direction</code>. The search is relative to the virtual view, i.e. a
+ * descendant of the host View, with the given <code>virtualViewId</code> or
+ * the host View itself <code>virtualViewId</code> equals to {@link View#NO_ID}.
+ *
+ * <strong>Note:</strong> Normally the system is responsible to transparently find
+ * the next view to take accessibility focus but for virtual view hierarchies
+ * it is a responsibility of this provider's implementor to compute the next
+ * focusable.
+ *
+ * @param direction The direction in which to search for a focus candidate.
+ * Values are
+ * {@link View#ACCESSIBILITY_FOCUS_IN},
+ * {@link View#ACCESSIBILITY_FOCUS_OUT},
+ * {@link View#ACCESSIBILITY_FOCUS_FORWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_BACKWARD},
+ * {@link View#ACCESSIBILITY_FOCUS_UP},
+ * {@link View#ACCESSIBILITY_FOCUS_DOWN},
+ * {@link View#ACCESSIBILITY_FOCUS_LEFT},
+ * {@link View#ACCESSIBILITY_FOCUS_RIGHT}.
+ * @param virtualViewId A client defined virtual view id which defined
+ * the root of the tree in which to perform the search.
+ * @return A list of node info.
+ *
+ * @see #createAccessibilityNodeInfo(int)
+ * @see AccessibilityNodeInfo
+ */
+ public AccessibilityNodeInfo accessibilityFocusSearch(int direction, int virtualViewId) {
+ return null;
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index d25b3db..78a7d46 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -62,6 +62,7 @@
private static final int PROPERTY_PASSWORD = 0x00000004;
private static final int PROPERTY_FULL_SCREEN = 0x00000080;
private static final int PROPERTY_SCROLLABLE = 0x00000100;
+ private static final int PROPERTY_IMPORTANT_FOR_ACCESSIBILITY = 0x00000200;
private static final int GET_SOURCE_PREFETCH_FLAGS =
AccessibilityNodeInfo.FLAG_PREFETCH_PREDECESSORS
@@ -77,7 +78,7 @@
private boolean mIsInPool;
boolean mSealed;
- int mBooleanProperties;
+ int mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
int mCurrentItemIndex = UNDEFINED;
int mItemCount = UNDEFINED;
int mFromIndex = UNDEFINED;
@@ -134,6 +135,8 @@
*/
public void setSource(View root, int virtualDescendantId) {
enforceNotSealed();
+ final boolean important = (root != null) ? root.isImportantForAccessibility() : true;
+ setBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY, important);
mSourceWindowId = (root != null) ? root.getAccessibilityWindowId() : UNDEFINED;
final int rootViewId = (root != null) ? root.getAccessibilityViewId() : UNDEFINED;
mSourceNodeId = AccessibilityNodeInfo.makeNodeId(rootViewId, virtualDescendantId);
@@ -274,6 +277,23 @@
}
/**
+ * Gets if the source is important for accessibility.
+ *
+ * <strong>Note:</strong> Used only internally to determine whether
+ * to deliver the event to a given accessibility service since some
+ * services may want to regard all views for accessibility while others
+ * may want to regard only the important views for accessibility.
+ *
+ * @return True if the source is important for accessibility,
+ * false otherwise.
+ *
+ * @hide
+ */
+ public boolean isImportantForAccessibility() {
+ return getBooleanProperty(PROPERTY_IMPORTANT_FOR_ACCESSIBILITY);
+ }
+
+ /**
* Gets the number of items that can be visited.
*
* @return The number of items.
@@ -755,7 +775,7 @@
*/
void clear() {
mSealed = false;
- mBooleanProperties = 0;
+ mBooleanProperties = PROPERTY_IMPORTANT_FOR_ACCESSIBILITY;
mCurrentItemIndex = UNDEFINED;
mItemCount = UNDEFINED;
mFromIndex = UNDEFINED;
diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
index fc3651c..8182d29 100644
--- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl
@@ -28,18 +28,26 @@
oneway interface IAccessibilityInteractionConnection {
void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int prefetchFlags,
- int interrogatingPid, long interrogatingTid);
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
void findAccessibilityNodeInfoByViewId(long accessibilityNodeId, int id, int interactionId,
- IAccessibilityInteractionConnectionCallback callback,
- int interrogatingPid, long interrogatingTid);
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
+
+ void findFocus(long accessibilityNodeId, int interactionId, int focusType,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
+ long interrogatingTid);
+
+ void focusSearch(long accessibilityNodeId, int interactionId, int direction,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid);
void performAccessibilityAction(long accessibilityNodeId, int action, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, int interrogatingPid,
+ IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid);
}
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 320c75d..5b5134a 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -19,7 +19,7 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.accessibilityservice.IEventListener;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -49,7 +49,8 @@
void removeAccessibilityInteractionConnection(IWindow windowToken);
- void registerUiTestAutomationService(IEventListener listener, in AccessibilityServiceInfo info);
+ void registerUiTestAutomationService(IAccessibilityServiceClient client,
+ in AccessibilityServiceInfo info);
- void unregisterUiTestAutomationService(IEventListener listener);
+ void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
}
diff --git a/core/java/android/webkit/CallbackProxy.java b/core/java/android/webkit/CallbackProxy.java
index 800ebc8..64fbdd5 100644
--- a/core/java/android/webkit/CallbackProxy.java
+++ b/core/java/android/webkit/CallbackProxy.java
@@ -147,6 +147,40 @@
}
}
+ private class JsResultReceiver implements JsResult.ResultReceiver {
+ // This prevents a user from interacting with the result before WebCore is
+ // ready to handle it.
+ private boolean mReady;
+ // Tells us if the user tried to confirm or cancel the result before WebCore
+ // is ready.
+ private boolean mTriedToNotifyBeforeReady;
+
+ public JsPromptResult mJsResult = new JsPromptResult(this);
+
+ final void setReady() {
+ mReady = true;
+ if (mTriedToNotifyBeforeReady) {
+ notifyCallbackProxy();
+ }
+ }
+
+ /* Wake up the WebCore thread. */
+ @Override
+ public void onJsResultComplete(JsResult result) {
+ if (mReady) {
+ notifyCallbackProxy();
+ } else {
+ mTriedToNotifyBeforeReady = true;
+ }
+ }
+
+ private void notifyCallbackProxy() {
+ synchronized (CallbackProxy.this) {
+ CallbackProxy.this.notify();
+ }
+ }
+}
+
/**
* Construct a new CallbackProxy.
*/
@@ -479,18 +513,18 @@
String databaseIdentifier =
(String) map.get("databaseIdentifier");
String url = (String) map.get("url");
- long currentQuota =
- ((Long) map.get("currentQuota")).longValue();
- long totalUsedQuota =
- ((Long) map.get("totalUsedQuota")).longValue();
- long estimatedSize =
- ((Long) map.get("estimatedSize")).longValue();
+ long quota =
+ ((Long) map.get("quota")).longValue();
+ long totalQuota =
+ ((Long) map.get("totalQuota")).longValue();
+ long estimatedDatabaseSize =
+ ((Long) map.get("estimatedDatabaseSize")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
mWebChromeClient.onExceededDatabaseQuota(url,
- databaseIdentifier, currentQuota, estimatedSize,
- totalUsedQuota, quotaUpdater);
+ databaseIdentifier, quota, estimatedDatabaseSize,
+ totalQuota, quotaUpdater);
}
break;
@@ -498,15 +532,15 @@
if (mWebChromeClient != null) {
HashMap<String, Object> map =
(HashMap<String, Object>) msg.obj;
- long spaceNeeded =
- ((Long) map.get("spaceNeeded")).longValue();
- long totalUsedQuota =
- ((Long) map.get("totalUsedQuota")).longValue();
+ long requiredStorage =
+ ((Long) map.get("requiredStorage")).longValue();
+ long quota =
+ ((Long) map.get("quota")).longValue();
WebStorage.QuotaUpdater quotaUpdater =
(WebStorage.QuotaUpdater) map.get("quotaUpdater");
- mWebChromeClient.onReachedMaxAppCacheSize(spaceNeeded,
- totalUsedQuota, quotaUpdater);
+ mWebChromeClient.onReachedMaxAppCacheSize(requiredStorage,
+ quota, quotaUpdater);
}
break;
@@ -531,14 +565,15 @@
case JS_ALERT:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsAlert(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
@@ -561,20 +596,21 @@
})
.show();
}
- res.setReady();
+ receiver.setReady();
}
break;
case JS_CONFIRM:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsConfirm(mWebView.getWebView(), url, message,
res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
new AlertDialog.Builder(mContext)
@@ -605,13 +641,14 @@
}
// Tell the JsResult that it is ready for client
// interaction.
- res.setReady();
+ receiver.setReady();
}
break;
case JS_PROMPT:
if (mWebChromeClient != null) {
- final JsPromptResult res = (JsPromptResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsPromptResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String defaultVal = msg.getData().getString("default");
String url = msg.getData().getString("url");
@@ -619,7 +656,7 @@
defaultVal, res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
final LayoutInflater factory = LayoutInflater
@@ -662,20 +699,21 @@
}
// Tell the JsResult that it is ready for client
// interaction.
- res.setReady();
+ receiver.setReady();
}
break;
case JS_UNLOAD:
if (mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
String message = msg.getData().getString("message");
String url = msg.getData().getString("url");
if (!mWebChromeClient.onJsBeforeUnload(mWebView.getWebView(), url,
message, res)) {
if (!canShowAlertDialog()) {
res.cancel();
- res.setReady();
+ receiver.setReady();
break;
}
final String m = mContext.getString(
@@ -700,19 +738,20 @@
})
.show();
}
- res.setReady();
+ receiver.setReady();
}
break;
case JS_TIMEOUT:
if(mWebChromeClient != null) {
- final JsResult res = (JsResult) msg.obj;
+ final JsResultReceiver receiver = (JsResultReceiver) msg.obj;
+ final JsResult res = receiver.mJsResult;
if(mWebChromeClient.onJsTimeout()) {
res.confirm();
} else {
res.cancel();
}
- res.setReady();
+ receiver.setReady();
}
break;
@@ -791,7 +830,8 @@
case OPEN_FILE_CHOOSER:
if (mWebChromeClient != null) {
UploadFileMessageData data = (UploadFileMessageData)msg.obj;
- mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType());
+ mWebChromeClient.openFileChooser(data.getUploadFile(), data.getAcceptType(),
+ data.getCapture());
}
break;
@@ -1331,7 +1371,7 @@
if (mWebChromeClient == null) {
return;
}
- JsResult result = new JsResult(this, false);
+ JsResultReceiver result = new JsResultReceiver();
Message alert = obtainMessage(JS_ALERT, result);
alert.getData().putString("message", message);
alert.getData().putString("url", url);
@@ -1352,7 +1392,7 @@
if (mWebChromeClient == null) {
return false;
}
- JsResult result = new JsResult(this, false);
+ JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_CONFIRM, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
@@ -1365,7 +1405,7 @@
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
public String onJsPrompt(String url, String message, String defaultValue) {
@@ -1374,7 +1414,7 @@
if (mWebChromeClient == null) {
return null;
}
- JsPromptResult result = new JsPromptResult(this);
+ JsResultReceiver result = new JsResultReceiver();
Message prompt = obtainMessage(JS_PROMPT, result);
prompt.getData().putString("message", message);
prompt.getData().putString("default", defaultValue);
@@ -1388,7 +1428,7 @@
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getStringResult();
+ return result.mJsResult.getStringResult();
}
public boolean onJsBeforeUnload(String url, String message) {
@@ -1397,7 +1437,7 @@
if (mWebChromeClient == null) {
return true;
}
- JsResult result = new JsResult(this, true);
+ JsResultReceiver result = new JsResultReceiver();
Message confirm = obtainMessage(JS_UNLOAD, result);
confirm.getData().putString("message", message);
confirm.getData().putString("url", url);
@@ -1410,7 +1450,7 @@
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
/**
@@ -1422,19 +1462,21 @@
* @param url The URL that caused the quota overflow.
* @param databaseIdentifier The identifier of the database that the
* transaction that caused the overflow was running on.
- * @param currentQuota The current quota the origin is allowed.
- * @param estimatedSize The estimated size of the database.
- * @param totalUsedQuota is the sum of all origins' quota.
+ * @param quota The current quota the origin is allowed.
+ * @param estimatedDatabaseSize The estimated size of the database.
+ * @param totalQuota is the sum of all origins' quota.
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny more
* quota has been made.
*/
public void onExceededDatabaseQuota(
- String url, String databaseIdentifier, long currentQuota,
- long estimatedSize, long totalUsedQuota,
+ String url, String databaseIdentifier, long quota,
+ long estimatedDatabaseSize, long totalQuota,
WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
- quotaUpdater.updateQuota(currentQuota);
+ // Native-side logic prevents the quota being updated to a smaller
+ // value.
+ quotaUpdater.updateQuota(quota);
return;
}
@@ -1442,9 +1484,9 @@
HashMap<String, Object> map = new HashMap();
map.put("databaseIdentifier", databaseIdentifier);
map.put("url", url);
- map.put("currentQuota", currentQuota);
- map.put("estimatedSize", estimatedSize);
- map.put("totalUsedQuota", totalUsedQuota);
+ map.put("quota", quota);
+ map.put("estimatedDatabaseSize", estimatedDatabaseSize);
+ map.put("totalQuota", totalQuota);
map.put("quotaUpdater", quotaUpdater);
exceededQuota.obj = map;
sendMessage(exceededQuota);
@@ -1453,24 +1495,26 @@
/**
* Called by WebViewCore to inform the Java side that the appcache has
* exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
- * @param totalUsedQuota is the sum of all origins' quota.
+ * @param requiredStorage is the amount of storage, in bytes, that would be
+ * needed in order for the last appcache operation to succeed.
+ * @param quota is the current quota (for all origins).
* @param quotaUpdater An instance of a class encapsulating a callback
* to WebViewCore to run when the decision to allow or deny a bigger
* app cache size has been made.
*/
- public void onReachedMaxAppCacheSize(long spaceNeeded,
- long totalUsedQuota, WebStorage.QuotaUpdater quotaUpdater) {
+ public void onReachedMaxAppCacheSize(long requiredStorage,
+ long quota, WebStorage.QuotaUpdater quotaUpdater) {
if (mWebChromeClient == null) {
- quotaUpdater.updateQuota(0);
+ // Native-side logic prevents the quota being updated to a smaller
+ // value.
+ quotaUpdater.updateQuota(quota);
return;
}
Message msg = obtainMessage(REACHED_APPCACHE_MAXSIZE);
HashMap<String, Object> map = new HashMap();
- map.put("spaceNeeded", spaceNeeded);
- map.put("totalUsedQuota", totalUsedQuota);
+ map.put("requiredStorage", requiredStorage);
+ map.put("quota", quota);
map.put("quotaUpdater", quotaUpdater);
msg.obj = map;
sendMessage(msg);
@@ -1540,7 +1584,7 @@
if (mWebChromeClient == null) {
return true;
}
- JsResult result = new JsResult(this, true);
+ JsResultReceiver result = new JsResultReceiver();
Message timeout = obtainMessage(JS_TIMEOUT, result);
synchronized (this) {
sendMessage(timeout);
@@ -1551,7 +1595,7 @@
Log.e(LOGTAG, Log.getStackTraceString(e));
}
}
- return result.getResult();
+ return result.mJsResult.getResult();
}
public void getVisitedHistory(ValueCallback<String[]> callback) {
@@ -1566,10 +1610,12 @@
private static class UploadFileMessageData {
private UploadFile mCallback;
private String mAcceptType;
+ private String mCapture;
- public UploadFileMessageData(UploadFile uploadFile, String acceptType) {
+ public UploadFileMessageData(UploadFile uploadFile, String acceptType, String capture) {
mCallback = uploadFile;
mAcceptType = acceptType;
+ mCapture = capture;
}
public UploadFile getUploadFile() {
@@ -1579,6 +1625,10 @@
public String getAcceptType() {
return mAcceptType;
}
+
+ public String getCapture() {
+ return mCapture;
+ }
}
private class UploadFile implements ValueCallback<Uri> {
@@ -1597,13 +1647,13 @@
/**
* Called by WebViewCore to open a file chooser.
*/
- /* package */ Uri openFileChooser(String acceptType) {
+ /* package */ Uri openFileChooser(String acceptType, String capture) {
if (mWebChromeClient == null) {
return null;
}
Message myMessage = obtainMessage(OPEN_FILE_CHOOSER);
UploadFile uploadFile = new UploadFile();
- UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType);
+ UploadFileMessageData data = new UploadFileMessageData(uploadFile, acceptType, capture);
myMessage.obj = data;
synchronized (this) {
sendMessage(myMessage);
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index 5f7ef41..2997c1a 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -26,7 +26,7 @@
* Manages the cookies used by an application's {@link WebView} instances.
* Cookies are manipulated according to RFC2109.
*/
-public final class CookieManager {
+public class CookieManager {
private static CookieManager sRef;
diff --git a/core/java/android/webkit/DeviceMotionAndOrientationManager.java b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
index 79b78d8..ea1e9ff 100644
--- a/core/java/android/webkit/DeviceMotionAndOrientationManager.java
+++ b/core/java/android/webkit/DeviceMotionAndOrientationManager.java
@@ -22,9 +22,8 @@
*
* This could be part of WebViewCore, but have moved it to its own class to
* avoid bloat there.
- * @hide
*/
-public final class DeviceMotionAndOrientationManager {
+final class DeviceMotionAndOrientationManager {
private WebViewCore mWebViewCore;
public DeviceMotionAndOrientationManager(WebViewCore webViewCore) {
@@ -32,12 +31,12 @@
}
/**
- * Sets whether the Page for this WebViewCore should use a mock DeviceOrientation
+ * Sets that the Page for this WebViewCore should use a mock DeviceOrientation
* client.
*/
- public void useMock() {
+ public void setUseMock() {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
- nativeUseMock(mWebViewCore);
+ nativeSetUseMock(mWebViewCore);
}
/**
@@ -66,7 +65,7 @@
}
// Native functions
- private static native void nativeUseMock(WebViewCore webViewCore);
+ private static native void nativeSetUseMock(WebViewCore webViewCore);
private static native void nativeSetMockOrientation(WebViewCore webViewCore,
boolean canProvideAlpha, double alpha, boolean canProvideBeta, double beta,
boolean canProvideGamma, double gamma);
diff --git a/core/java/android/webkit/DeviceMotionService.java b/core/java/android/webkit/DeviceMotionService.java
index b4d5759..9121429 100755
--- a/core/java/android/webkit/DeviceMotionService.java
+++ b/core/java/android/webkit/DeviceMotionService.java
@@ -153,6 +153,7 @@
* SensorEventListener implementation.
* Callbacks happen on the thread on which we registered - the WebCore thread.
*/
+ @Override
public void onSensorChanged(SensorEvent event) {
assert(event.values.length == 3);
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -170,6 +171,7 @@
}
}
+ @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
}
diff --git a/core/java/android/webkit/DeviceOrientationService.java b/core/java/android/webkit/DeviceOrientationService.java
index 47c8ab7..2e8656c 100755
--- a/core/java/android/webkit/DeviceOrientationService.java
+++ b/core/java/android/webkit/DeviceOrientationService.java
@@ -184,6 +184,7 @@
* SensorEventListener implementation.
* Callbacks happen on the thread on which we registered - the WebCore thread.
*/
+ @Override
public void onSensorChanged(SensorEvent event) {
assert(event.values.length == 3);
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
@@ -217,6 +218,7 @@
}
}
+ @Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
assert WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName());
}
diff --git a/core/java/android/webkit/GeolocationPermissions.java b/core/java/android/webkit/GeolocationPermissions.java
index 93eb082..1441541 100755
--- a/core/java/android/webkit/GeolocationPermissions.java
+++ b/core/java/android/webkit/GeolocationPermissions.java
@@ -50,7 +50,7 @@
// Within WebKit, Geolocation permissions may be applied either temporarily
// (for the duration of the page) or permanently. This class deals only with
// permanent permissions.
-public final class GeolocationPermissions {
+public class GeolocationPermissions {
/**
* A callback interface used by the host application to set the Geolocation
* permission state for an origin.
@@ -293,6 +293,16 @@
postMessage(Message.obtain(null, CLEAR_ALL));
}
+ /**
+ * This class should not be instantiated directly, applications must only use
+ * {@link #getInstance()} to obtain the instance.
+ * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+ * applications using it would receive a non-functional instance of this class (there was no
+ * way to call createHandler() and createUIHandler(), so it would not work).
+ * @hide
+ */
+ public GeolocationPermissions() {}
+
// Native functions, run on the WebKit thread.
private static native Set nativeGetOrigins();
private static native boolean nativeGetAllowed(String origin);
diff --git a/core/java/android/webkit/JsPromptResult.java b/core/java/android/webkit/JsPromptResult.java
index 9fcd1bc..a1bf124 100644
--- a/core/java/android/webkit/JsPromptResult.java
+++ b/core/java/android/webkit/JsPromptResult.java
@@ -18,11 +18,11 @@
/**
- * Public class for handling javascript prompt requests. A
- * JsDialogHandlerInterface implentation will receive a jsPrompt call with a
- * JsPromptResult parameter. This parameter is used to return a result to
- * WebView. The client can call cancel() to cancel the dialog or confirm() with
- * the user's input to confirm the dialog.
+ * Public class for handling JavaScript prompt requests. The WebChromeClient will receive a
+ * {@link WebChromeClient#onJsPrompt(WebView, String, String, String, JsPromptResult)} call with a
+ * JsPromptResult instance as a parameter. This parameter is used to return the result of this user
+ * dialog prompt back to the WebView instance. The client can call cancel() to cancel the dialog or
+ * confirm() with the user's input to confirm the dialog.
*/
public class JsPromptResult extends JsResult {
// String result of the prompt
@@ -36,17 +36,17 @@
confirm();
}
- /*package*/ JsPromptResult(CallbackProxy proxy) {
- super(proxy, /* unused */ false);
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public JsPromptResult(ResultReceiver receiver) {
+ super(receiver);
}
- /*package*/ String getStringResult() {
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public String getStringResult() {
return mStringResult;
}
-
- @Override
- /*package*/ void handleDefault() {
- mStringResult = null;
- super.handleDefault();
- }
}
diff --git a/core/java/android/webkit/JsResult.java b/core/java/android/webkit/JsResult.java
index e61ab21..e4e6851 100644
--- a/core/java/android/webkit/JsResult.java
+++ b/core/java/android/webkit/JsResult.java
@@ -16,23 +16,24 @@
package android.webkit;
-
+/**
+ * An instance of this class is passed as a parameter in various {@link WebChromeClient} action
+ * notifications. The object is used as a handle onto the underlying JavaScript-originated request,
+ * and provides a means for the client to indicate whether this action should proceed.
+ */
public class JsResult {
- // This prevents a user from interacting with the result before WebCore is
- // ready to handle it.
- private boolean mReady;
- // Tells us if the user tried to confirm or cancel the result before WebCore
- // is ready.
- private boolean mTriedToNotifyBeforeReady;
- // This is a basic result of a confirm or prompt dialog.
- protected boolean mResult;
/**
- * This is the caller of the prompt and is the object that is waiting.
- * @hide
+ * Callback interface, implemented by the WebViewProvider implementation to receive
+ * notifications when the JavaScript result represented by a JsResult instance has
+ * @hide Only for use by WebViewProvider implementations
*/
- protected final CallbackProxy mProxy;
- // This is the default value of the result.
- private final boolean mDefaultValue;
+ public interface ResultReceiver {
+ public void onJsResultComplete(JsResult result);
+ }
+ // This is the caller of the prompt and is the object that is waiting.
+ private final ResultReceiver mReceiver;
+ // This is a basic result of a confirm or prompt dialog.
+ private boolean mResult;
/**
* Handle the result if the user cancelled the dialog.
@@ -50,36 +51,22 @@
wakeUp();
}
- /*package*/ JsResult(CallbackProxy proxy, boolean defaultVal) {
- mProxy = proxy;
- mDefaultValue = defaultVal;
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public JsResult(ResultReceiver receiver) {
+ mReceiver = receiver;
}
- /*package*/ final boolean getResult() {
+ /**
+ * @hide Only for use by WebViewProvider implementations
+ */
+ public final boolean getResult() {
return mResult;
}
- /*package*/ final void setReady() {
- mReady = true;
- if (mTriedToNotifyBeforeReady) {
- wakeUp();
- }
- }
-
- /*package*/ void handleDefault() {
- setReady();
- mResult = mDefaultValue;
- wakeUp();
- }
-
- /* Wake up the WebCore thread. */
- protected final void wakeUp() {
- if (mReady) {
- synchronized (mProxy) {
- mProxy.notify();
- }
- } else {
- mTriedToNotifyBeforeReady = true;
- }
+ /* Notify the caller that the JsResult has completed */
+ private final void wakeUp() {
+ mReceiver.onJsResultComplete(this);
}
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index a6ef0ce..4e8790b 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -216,37 +216,54 @@
}
/**
- * Tell the client that the database quota for the origin has been exceeded.
- * @param url The URL that triggered the notification
- * @param databaseIdentifier The identifier of the database that caused the
- * quota overflow.
- * @param currentQuota The current quota for the origin.
- * @param estimatedSize The estimated size of the database.
- * @param totalUsedQuota is the sum of all origins' quota.
- * @param quotaUpdater A callback to inform the WebCore thread that a new
- * quota is available. This callback must always be executed at some
- * point to ensure that the sleeping WebCore thread is woken up.
+ * Tell the client that the quota has been exceeded for the Web SQL Database
+ * API for a particular origin and request a new quota. The client must
+ * respond by invoking the
+ * {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)}
+ * method of the supplied {@link WebStorage.QuotaUpdater} instance. The
+ * minimum value that can be set for the new quota is the current quota. The
+ * default implementation responds with the current quota, so the quota will
+ * not be increased.
+ * @param url The URL of the page that triggered the notification
+ * @param databaseIdentifier The identifier of the database where the quota
+ * was exceeded.
+ * @param quota The quota for the origin, in bytes
+ * @param estimatedDatabaseSize The estimated size of the offending
+ * database, in bytes
+ * @param totalQuota The total quota for all origins, in bytes
+ * @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which
+ * must be used to inform the WebView of the new quota.
*/
+ // Note that the callback must always be executed at some point to ensure
+ // that the sleeping WebCore thread is woken up.
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
- long currentQuota, long estimatedSize, long totalUsedQuota,
- WebStorage.QuotaUpdater quotaUpdater) {
+ long quota, long estimatedDatabaseSize, long totalQuota,
+ WebStorage.QuotaUpdater quotaUpdater) {
// This default implementation passes the current quota back to WebCore.
// WebCore will interpret this that new quota was declined.
- quotaUpdater.updateQuota(currentQuota);
+ quotaUpdater.updateQuota(quota);
}
/**
- * Tell the client that the Application Cache has exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
- * @param totalUsedQuota is the sum of all origins' quota.
- * @param quotaUpdater A callback to inform the WebCore thread that a new
- * app cache size is available. This callback must always be executed at
- * some point to ensure that the sleeping WebCore thread is woken up.
+ * Tell the client that the quota has been reached for the Application Cache
+ * API and request a new quota. The client must respond by invoking the
+ * {@link WebStorage.QuotaUpdater#updateQuota(long) updateQuota(long)}
+ * method of the supplied {@link WebStorage.QuotaUpdater} instance. The
+ * minimum value that can be set for the new quota is the current quota. The
+ * default implementation responds with the current quota, so the quota will
+ * not be increased.
+ * @param requiredStorage The amount of storage required by the Application
+ * Cache operation that triggered this notification,
+ * in bytes.
+ * @param quota The quota, in bytes
+ * @param quotaUpdater An instance of {@link WebStorage.QuotaUpdater} which
+ * must be used to inform the WebView of the new quota.
*/
- public void onReachedMaxAppCacheSize(long spaceNeeded, long totalUsedQuota,
+ // Note that the callback must always be executed at some point to ensure
+ // that the sleeping WebCore thread is woken up.
+ public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
WebStorage.QuotaUpdater quotaUpdater) {
- quotaUpdater.updateQuota(0);
+ quotaUpdater.updateQuota(quota);
}
/**
@@ -346,9 +363,11 @@
* onReceiveValue must be called to wake up the thread.a
* @param acceptType The value of the 'accept' attribute of the input tag
* associated with this file picker.
+ * @param capture The value of the 'capture' attribute of the input tag
+ * associated with this file picker.
* @hide
*/
- public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType) {
+ public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
uploadFile.onReceiveValue(null);
}
diff --git a/core/java/android/webkit/WebIconDatabase.java b/core/java/android/webkit/WebIconDatabase.java
index 54dfab3..9299b71 100644
--- a/core/java/android/webkit/WebIconDatabase.java
+++ b/core/java/android/webkit/WebIconDatabase.java
@@ -35,7 +35,7 @@
* WebIconDatabase object is a single instance and all methods operate on that
* single object.
*/
-public final class WebIconDatabase {
+public class WebIconDatabase {
private static final String LOGTAG = "WebIconDatabase";
// Global instance of a WebIconDatabase
private static WebIconDatabase sIconDatabase;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index cddd7ab..105285c 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -170,45 +170,62 @@
}
/**
- * Set whether the WebView supports zoom
+ * Sets whether the WebView should support zooming using its on-screen zoom
+ * controls and gestures. The particular zoom mechanisms that should be used
+ * can be set with {@link #setBuiltInZoomControls}. This setting does not
+ * affect zooming performed using the {@link WebView#zoomIn()} and
+ * {@link WebView#zoomOut()} methods.
+ * @param support Whether the WebView should support zoom.
*/
public void setSupportZoom(boolean support) {
throw new MustOverrideException();
}
/**
- * Returns whether the WebView supports zoom
+ * Returns true if the WebView supports zoom. The default is true.
+ * @return True if the WebView supports zoom.
*/
public boolean supportZoom() {
throw new MustOverrideException();
}
/**
- * Sets whether the zoom mechanism built into WebView is used.
+ * Sets whether the WebView should use its built-in zoom mechanisms, as
+ * opposed to separate zoom controls. The built-in zoom mechanisms comprise
+ * on-screen zoom controls, which are displayed over the WebView's content,
+ * and the use of a pinch gesture to control zooming. Whether or not these
+ * on-screen controls are displayed can be set with {@link #setDisplayZoomControls}.
+ * The separate zoom controls are no longer supported, so it is recommended
+ * that this setting is always enabled.
+ * @param enabled Whether the WebView should use the built-in zoom mechanism.
*/
public void setBuiltInZoomControls(boolean enabled) {
throw new MustOverrideException();
}
/**
- * Returns true if the zoom mechanism built into WebView is being used.
+ * Returns true if the zoom mechanisms built into WebView are being used.
+ * The default is false.
+ * @return True if the zoom mechanisms built into WebView are being used.
*/
public boolean getBuiltInZoomControls() {
throw new MustOverrideException();
}
/**
- * Sets whether the on screen zoom buttons are used.
- * A combination of built in zoom controls enabled
- * and on screen zoom controls disabled allows for pinch to zoom
- * to work without the on screen controls
+ * Sets whether the WebView should display on-screen zoom controls when
+ * using the built-in zoom mechanisms. See {@link #setBuiltInZoomControls}.
+ * @param enabled Whether the WebView should display on-screen zoom controls.
*/
public void setDisplayZoomControls(boolean enabled) {
throw new MustOverrideException();
}
/**
- * Returns true if the on screen zoom buttons are being used.
+ * Returns true if the WebView displays on-screen zoom controls when using
+ * the built-in zoom mechanisms. The default is true.
+ * @return True if the WebView displays on-screen zoom controls when using
+ * the built-in zoom mechanisms.
*/
public boolean getDisplayZoomControls() {
throw new MustOverrideException();
diff --git a/core/java/android/webkit/WebStorage.java b/core/java/android/webkit/WebStorage.java
index 2300c2e..041791b 100644
--- a/core/java/android/webkit/WebStorage.java
+++ b/core/java/android/webkit/WebStorage.java
@@ -25,19 +25,34 @@
import java.util.Set;
/**
- * Functionality for manipulating the webstorage databases.
+ * This class is used to manage the JavaScript storage APIs provided by the
+ * {@link WebView}. It manages the Application Cache API, the Web SQL Database
+ * API and the HTML5 Web Storage API.
+ *
+ * The Web SQL Database API provides storage which is private to a given
+ * origin, where an origin comprises the host, scheme and port of a URI.
+ * Similarly, use of the Application Cache API can be attributed to an origin.
+ * This class provides access to the storage use and quotas for these APIs for
+ * a given origin. Origins are represented using {@link WebStorage.Origin}.
*/
-public final class WebStorage {
+public class WebStorage {
/**
- * Encapsulates a callback function to be executed when a new quota is made
- * available. We primarily want this to allow us to call back the sleeping
- * WebCore thread from outside the WebViewCore class (as the native call
- * is private). It is imperative that this the setDatabaseQuota method is
- * executed once a decision to either allow or deny new quota is made,
- * otherwise the WebCore thread will remain asleep.
+ * Encapsulates a callback function which is used to provide a new quota
+ * for a JavaScript storage API. See
+ * {@link WebChromeClient#onExceededDatabaseQuota} and
+ * {@link WebChromeClient#onReachedMaxAppCacheSize}.
*/
+ // We primarily want this to allow us to call back the sleeping WebCore
+ // thread from outside the WebViewCore class (as the native call is
+ // private). It is imperative that the setDatabaseQuota method is
+ // executed after a decision to either allow or deny new quota is made,
+ // otherwise the WebCore thread will remain asleep.
public interface QuotaUpdater {
+ /**
+ * Provide a new quota, specified in bytes.
+ * @param newQuota The new quota, in bytes
+ */
public void updateQuota(long newQuota);
};
@@ -70,7 +85,9 @@
private Handler mUIHandler = null;
/**
- * Class containing the HTML5 database quota and usage for an origin.
+ * This class encapsulates information about the amount of storage
+ * currently used by an origin for the JavaScript storage APIs.
+ * See {@link WebStorage} for details.
*/
public static class Origin {
private String mOrigin = null;
@@ -93,28 +110,32 @@
}
/**
- * An origin string is created using WebCore::SecurityOrigin::toString().
- * Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
- * the port if the port is the default for the protocol. Eg
- * http://www.google.com and http://www.google.com:80 both record a port
- * of 0 and hence toString() == 'http://www.google.com' for both.
- * @return The origin string.
+ * Get the string representation of this origin.
+ * @return The string representation of this origin
*/
+ // An origin string is created using WebCore::SecurityOrigin::toString().
+ // Note that WebCore::SecurityOrigin uses 0 (which is not printed) for
+ // the port if the port is the default for the protocol. Eg
+ // http://www.google.com and http://www.google.com:80 both record a port
+ // of 0 and hence toString() == 'http://www.google.com' for both.
public String getOrigin() {
return mOrigin;
}
/**
- * Returns the quota for this origin's HTML5 database.
- * @return The quota in bytes.
+ * Get the quota for this origin, for the Web SQL Database API, in
+ * bytes. If this origin does not use the Web SQL Database API, this
+ * quota will be set to zero.
+ * @return The quota, in bytes.
*/
public long getQuota() {
return mQuota;
}
/**
- * Returns the usage for this origin's HTML5 database.
- * @return The usage in bytes.
+ * Get the total amount of storage currently being used by this origin,
+ * for all JavaScript storage APIs, in bytes.
+ * @return The total amount of storage, in bytes.
*/
public long getUsage() {
return mUsage;
@@ -122,8 +143,8 @@
}
/**
- * @hide
* Message handler, UI side
+ * @hide
*/
public void createUIHandler() {
if (mUIHandler == null) {
@@ -156,8 +177,8 @@
}
/**
+ * Message handler, WebCore side
* @hide
- * Message handler, webcore side
*/
public synchronized void createHandler() {
if (mHandler == null) {
@@ -231,19 +252,22 @@
/*
* When calling getOrigins(), getUsageForOrigin() and getQuotaForOrigin(),
- * we need to get the values from webcore, but we cannot block while doing so
- * as we used to do, as this could result in a full deadlock (other webcore
+ * we need to get the values from WebCore, but we cannot block while doing so
+ * as we used to do, as this could result in a full deadlock (other WebCore
* messages received while we are still blocked here, see http://b/2127737).
*
* We have to do everything asynchronously, by providing a callback function.
- * We post a message on the webcore thread (mHandler) that will get the result
- * from webcore, and we post it back on the UI thread (using mUIHandler).
+ * We post a message on the WebCore thread (mHandler) that will get the result
+ * from WebCore, and we post it back on the UI thread (using mUIHandler).
* We can then use the callback function to return the value.
*/
/**
- * Returns a list of origins having a database. The Map is of type
- * Map<String, Origin>.
+ * Get the origins currently using either the Application Cache or Web SQL
+ * Database APIs. This method operates asynchronously, with the result
+ * being provided via a {@link ValueCallback}. The origins are provided as
+ * a map, of type {@code Map<String, WebStorage.Origin>}, from the string
+ * representation of the origin to a {@link WebStorage.Origin} object.
*/
public void getOrigins(ValueCallback<Map> callback) {
if (callback != null) {
@@ -269,7 +293,11 @@
}
/**
- * Returns the use for a given origin
+ * Get the amount of storage currently being used by both the Application
+ * Cache and Web SQL Database APIs by the given origin. The amount is given
+ * in bytes and the origin is specified using its string representation.
+ * This method operates asynchronously, with the result being provided via
+ * a {@link ValueCallback}.
*/
public void getUsageForOrigin(String origin, ValueCallback<Long> callback) {
if (callback == null) {
@@ -292,7 +320,11 @@
}
/**
- * Returns the quota for a given origin
+ * Get the storage quota for the Web SQL Database API for the given origin.
+ * The quota is given in bytes and the origin is specified using its string
+ * representation. This method operates asynchronously, with the result
+ * being provided via a {@link ValueCallback}. Note that a quota is not
+ * enforced on a per-origin basis for the Application Cache API.
*/
public void getQuotaForOrigin(String origin, ValueCallback<Long> callback) {
if (callback == null) {
@@ -315,7 +347,10 @@
}
/**
- * Set the quota for a given origin
+ * Set the storage quota for the Web SQL Database API for the given origin.
+ * The quota is specified in bytes and the origin is specified using its string
+ * representation. Note that a quota is not enforced on a per-origin basis
+ * for the Application Cache API.
*/
public void setQuotaForOrigin(String origin, long quota) {
if (origin != null) {
@@ -329,7 +364,9 @@
}
/**
- * Delete a given origin
+ * Clear the storage currently being used by both the Application Cache and
+ * Web SQL Database APIs by the given origin. The origin is specified using
+ * its string representation.
*/
public void deleteOrigin(String origin) {
if (origin != null) {
@@ -343,7 +380,9 @@
}
/**
- * Delete all databases
+ * Clear all storage currently being used by the JavaScript storage APIs.
+ * This includes the Application Cache, Web SQL Database and the HTML5 Web
+ * Storage APIs.
*/
public void deleteAllData() {
if (WebViewCore.THREAD_NAME.equals(Thread.currentThread().getName())) {
@@ -381,8 +420,8 @@
}
/**
- * Get the global instance of WebStorage.
- * @return A single instance of WebStorage.
+ * Get the singleton instance of this class.
+ * @return The singleton {@link WebStorage} instance.
*/
public static WebStorage getInstance() {
if (sWebStorage == null) {
@@ -404,7 +443,7 @@
}
/**
- * Run on the webcore thread
+ * Run on the WebCore thread
* set the local values with the current ones
*/
private void syncValues() {
@@ -418,6 +457,16 @@
}
}
+ /**
+ * This class should not be instantiated directly, applications must only use
+ * {@link #getInstance()} to obtain the instance.
+ * Note this constructor was erroneously public and published in SDK levels prior to 16, but
+ * applications using it would receive a non-functional instance of this class (there was no
+ * way to call createHandler() and createUIHandler(), so it would not work).
+ * @hide
+ */
+ public WebStorage() {}
+
// Native functions
private static native Set nativeGetOrigins();
private static native long nativeGetUsageForOrigin(String origin);
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 84632c6..5498622 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1281,8 +1281,10 @@
* @deprecated {@link #findAllAsync} is preferred.
* @see #setFindListener
*/
+ @Deprecated
public int findAll(String find) {
checkThread();
+ StrictMode.noteSlowCall("findAll blocks UI: prefer findAllAsync");
return mProvider.findAll(find);
}
@@ -1539,6 +1541,7 @@
*
* @deprecated The built-in zoom mechanism is preferred, see
* {@link WebSettings#setBuiltInZoomControls(boolean)}.
+ * @hide since API version 16.
*/
@Deprecated
public View getZoomControls() {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 5dc2681..893849b9 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -828,6 +828,13 @@
// if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing
// the screen all-the-time. Good for profiling our drawing code
static private final boolean AUTO_REDRAW_HACK = false;
+
+ // The rate at which edit text is scrolled in content pixels per millisecond
+ static private final float TEXT_SCROLL_RATE = 0.01f;
+
+ // The presumed scroll rate for the first scroll of edit text
+ static private final long TEXT_SCROLL_FIRST_SCROLL_MS = 16;
+
// true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK
private boolean mAutoRedraw;
@@ -853,6 +860,7 @@
boolean mIsEditingText = false;
ArrayList<Message> mBatchedTextChanges = new ArrayList<Message>();
boolean mIsBatchingTextChanges = false;
+ private long mLastEditScroll = 0;
private static class OnTrimMemoryListener implements ComponentCallbacks2 {
private static OnTrimMemoryListener sInstance = null;
@@ -1037,9 +1045,6 @@
// pages with the space bar, in pixels.
private static final int PAGE_SCROLL_OVERLAP = 24;
- // Time between successive calls to text scroll fling animation
- private static final int TEXT_SCROLL_ANIMATION_DELAY_MS = 16;
-
/**
* These prevent calling requestLayout if either dimension is fixed. This
* depends on the layout parameters and the measure specs.
@@ -1207,7 +1212,7 @@
static final int RELOCATE_AUTO_COMPLETE_POPUP = 146;
static final int FOCUS_NODE_CHANGED = 147;
static final int AUTOFILL_FORM = 148;
- static final int ANIMATE_TEXT_SCROLL = 149;
+ static final int SCROLL_EDIT_TEXT = 149;
static final int EDIT_TEXT_SIZE_CHANGED = 150;
static final int SHOW_CARET_HANDLE = 151;
static final int UPDATE_CONTENT_BOUNDS = 152;
@@ -4672,6 +4677,9 @@
* Select the word at the indicated content coordinates.
*/
boolean selectText(int x, int y) {
+ if (mWebViewCore == null) {
+ return false;
+ }
mWebViewCore.sendMessage(EventHub.SELECT_WORD_AT, x, y);
return true;
}
@@ -5049,8 +5057,8 @@
*
* debug only
*/
- public void useMockDeviceOrientation() {
- mWebViewCore.sendMessage(EventHub.USE_MOCK_DEVICE_ORIENTATION);
+ public void setUseMockDeviceOrientation() {
+ mWebViewCore.sendMessage(EventHub.SET_USE_MOCK_DEVICE_ORIENTATION);
}
/**
@@ -6002,9 +6010,9 @@
data.mNativeLayer = nativeScrollableLayer(
contentX, contentY, data.mNativeLayerRect, null);
data.mSlop = viewToContentDimension(mNavSlop);
- mTouchHighlightRegion.setEmpty();
+ removeTouchHighlight();
if (!mBlockWebkitViewMessages) {
- mTouchHighlightRequested = System.currentTimeMillis();
+ mTouchHighlightRequested = SystemClock.uptimeMillis();
mWebViewCore.sendMessageAtFrontOfQueue(
EventHub.HIT_TEST, data);
}
@@ -6091,6 +6099,11 @@
mSelectDraggingTextQuad.containsPoint(handleX, handleY);
boolean inEditBounds = mEditTextContentBounds
.contains(handleX, handleY);
+ if (mIsEditingText && !inEditBounds) {
+ beginScrollEdit();
+ } else {
+ endScrollEdit();
+ }
if (inCursorText || (mIsEditingText && !inEditBounds)) {
snapDraggingCursor();
}
@@ -6240,6 +6253,7 @@
break;
}
case MotionEvent.ACTION_UP: {
+ endScrollEdit();
if (!mConfirmMove && mIsEditingText && mSelectionStarted &&
mIsCaretSelection) {
showPasteWindow();
@@ -6335,6 +6349,86 @@
}
}
+ /**
+ * Returns the text scroll speed in content pixels per millisecond based on
+ * the touch location.
+ * @param coordinate The x or y touch coordinate in content space
+ * @param min The minimum coordinate (x or y) of the edit content bounds
+ * @param max The maximum coordinate (x or y) of the edit content bounds
+ */
+ private static float getTextScrollSpeed(int coordinate, int min, int max) {
+ if (coordinate < min) {
+ return (coordinate - min) * TEXT_SCROLL_RATE;
+ } else if (coordinate >= max) {
+ return (coordinate - max + 1) * TEXT_SCROLL_RATE;
+ } else {
+ return 0.0f;
+ }
+ }
+
+ private void beginScrollEdit() {
+ if (mLastEditScroll == 0) {
+ mLastEditScroll = SystemClock.uptimeMillis() -
+ TEXT_SCROLL_FIRST_SCROLL_MS;
+ scrollEditWithCursor();
+ }
+ }
+
+ private void endScrollEdit() {
+ mLastEditScroll = 0;
+ }
+
+ private static int getTextScrollDelta(float speed, long deltaT) {
+ float distance = speed * deltaT;
+ int intDistance = (int)Math.floor(distance);
+ float probability = distance - intDistance;
+ if (Math.random() < probability) {
+ intDistance++;
+ }
+ return intDistance;
+ }
+ /**
+ * Scrolls edit text a distance based on the last touch point,
+ * the last scroll time, and the edit text content bounds.
+ */
+ private void scrollEditWithCursor() {
+ if (mLastEditScroll != 0) {
+ int x = viewToContentX(mLastTouchX + getScrollX() + mSelectDraggingOffset.x);
+ float scrollSpeedX = getTextScrollSpeed(x, mEditTextContentBounds.left,
+ mEditTextContentBounds.right);
+ int y = viewToContentY(mLastTouchY + getScrollY() + mSelectDraggingOffset.y);
+ float scrollSpeedY = getTextScrollSpeed(y, mEditTextContentBounds.top,
+ mEditTextContentBounds.bottom);
+ if (scrollSpeedX == 0.0f && scrollSpeedY == 0.0f) {
+ endScrollEdit();
+ } else {
+ long currentTime = SystemClock.uptimeMillis();
+ long timeSinceLastUpdate = currentTime - mLastEditScroll;
+ int deltaX = getTextScrollDelta(scrollSpeedX, timeSinceLastUpdate);
+ int deltaY = getTextScrollDelta(scrollSpeedY, timeSinceLastUpdate);
+ mLastEditScroll = currentTime;
+ if (deltaX == 0 && deltaY == 0) {
+ // By probability no text scroll this time. Try again later.
+ mPrivateHandler.sendEmptyMessageDelayed(SCROLL_EDIT_TEXT,
+ TEXT_SCROLL_FIRST_SCROLL_MS);
+ } else {
+ int scrollX = getTextScrollX() + deltaX;
+ scrollX = Math.min(getMaxTextScrollX(), scrollX);
+ scrollX = Math.max(0, scrollX);
+ int scrollY = getTextScrollY() + deltaY;
+ scrollY = Math.min(getMaxTextScrollY(), scrollY);
+ scrollY = Math.max(0, scrollY);
+ scrollEditText(scrollX, scrollY);
+ int cursorX = mSelectDraggingCursor.x;
+ int cursorY = mSelectDraggingCursor.y;
+ mSelectDraggingCursor.set(x - deltaX, y - deltaY);
+ updateWebkitSelection();
+ mSelectDraggingCursor.set(cursorX, cursorY);
+ }
+ }
+ }
+ }
+
private void startTouch(float x, float y, long eventTime) {
// Remember where the motion event started
mStartTouchX = mLastTouchX = Math.round(x);
@@ -7053,7 +7147,8 @@
if (mFindIsUp) return false;
boolean result = false;
result = mWebViewPrivate.super_requestFocus(direction, previouslyFocusedRect);
- if (mWebViewCore.getSettings().getNeedInitialFocus() && !mWebView.isInTouchMode()) {
+ if (mWebViewCore.getSettings().getNeedInitialFocus()
+ && !mWebView.isInTouchMode()) {
// For cases such as GMail, where we gain focus from a direction,
// we want to move to the first available link.
// FIXME: If there are no visible links, we may not want to
@@ -7074,7 +7169,7 @@
default:
return result;
}
- // TODO: Send initial focus request to webkit (b/6108927)
+ mWebViewCore.sendMessage(EventHub.SET_INITIAL_FOCUS, fakeKeyDirection);
}
return result;
}
@@ -7673,10 +7768,6 @@
msg.arg1, /* unused */0);
break;
- case ANIMATE_TEXT_SCROLL:
- computeEditTextScroll();
- break;
-
case EDIT_TEXT_SIZE_CHANGED:
if (msg.arg1 == mFieldPointer) {
mEditTextContent.set((Rect)msg.obj);
@@ -7695,6 +7786,10 @@
mEditTextContentBounds.set((Rect) msg.obj);
break;
+ case SCROLL_EDIT_TEXT:
+ scrollEditWithCursor();
+ break;
+
default:
super.handleMessage(msg);
break;
@@ -7777,13 +7872,16 @@
if (mFocusedNode.mHasFocus && mFocusedNode.mEditable) {
return false;
}
- long delay = System.currentTimeMillis() - mTouchHighlightRequested;
+ long delay = SystemClock.uptimeMillis() - mTouchHighlightRequested;
if (delay < ViewConfiguration.getTapTimeout()) {
Rect r = mTouchHighlightRegion.getBounds();
mWebView.postInvalidateDelayed(delay, r.left, r.top, r.right, r.bottom);
return false;
}
- return true;
+ if (mInputDispatcher == null) {
+ return false;
+ }
+ return mInputDispatcher.shouldShowTapHighlight();
}
@@ -7893,8 +7991,8 @@
if (viewRect.width() < getWidth() >> 1
|| viewRect.height() < getHeight() >> 1) {
mTouchHighlightRegion.union(viewRect);
- } else {
- Log.w(LOGTAG, "Skip the huge selection rect:"
+ } else if (DebugFlags.WEB_VIEW) {
+ Log.d(LOGTAG, "Skip the huge selection rect:"
+ viewRect);
}
}
@@ -7995,16 +8093,17 @@
mWebView.invalidate();
}
- if (mPictureListener != null) {
- mPictureListener.onNewPicture(getWebView(), capturePicture());
- }
-
// update the zoom information based on the new picture
mZoomManager.onNewPicture(draw);
if (isPictureAfterFirstLayout) {
mViewManager.postReadyToDrawAll();
}
+ scrollEditWithCursor();
+
+ if (mPictureListener != null) {
+ mPictureListener.onNewPicture(getWebView(), capturePicture());
+ }
}
/**
@@ -8047,13 +8146,6 @@
invalidate();
}
- private void computeEditTextScroll() {
- if (mEditTextScroller.computeScrollOffset()) {
- scrollEditText(mEditTextScroller.getCurrX(),
- mEditTextScroller.getCurrY());
- }
- }
-
private void scrollEditText(int scrollX, int scrollY) {
// Scrollable edit text. Scroll it.
float maxScrollX = getMaxTextScrollX();
@@ -8061,8 +8153,6 @@
mEditTextContent.offsetTo(-scrollX, -scrollY);
mWebViewCore.sendMessageAtFrontOfQueue(EventHub.SCROLL_TEXT_INPUT, 0,
scrollY, (Float)scrollPercentX);
- mPrivateHandler.sendEmptyMessageDelayed(ANIMATE_TEXT_SCROLL,
- TEXT_SCROLL_ANIMATION_DELAY_MS);
}
private void beginTextBatch() {
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 6390ffe..e2880d6 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -394,10 +394,12 @@
* Called by JNI. Open a file chooser to upload a file.
* @param acceptType The value of the 'accept' attribute of the
* input tag associated with this file picker.
+ * @param capture The value of the 'capture' attribute of the
+ * input tag associated with this file picker.
* @return String version of the URI.
*/
- private String openFileChooser(String acceptType) {
- Uri uri = mCallbackProxy.openFileChooser(acceptType);
+ private String openFileChooser(String acceptType, String capture) {
+ Uri uri = mCallbackProxy.openFileChooser(acceptType, capture);
if (uri != null) {
String filePath = "";
// Note - querying for MediaStore.Images.Media.DATA
@@ -428,38 +430,38 @@
* Notify the browser that the origin has exceeded it's database quota.
* @param url The URL that caused the overflow.
* @param databaseIdentifier The identifier of the database.
- * @param currentQuota The current quota for the origin.
- * @param estimatedSize The estimated size of the database.
+ * @param quota The current quota for the origin.
+ * @param estimatedDatabaseSize The estimated size of the database.
*/
protected void exceededDatabaseQuota(String url,
String databaseIdentifier,
- long currentQuota,
- long estimatedSize) {
+ long quota,
+ long estimatedDatabaseSize) {
// Inform the callback proxy of the quota overflow. Send an object
// that encapsulates a call to the nativeSetDatabaseQuota method to
// awaken the sleeping webcore thread when a decision from the
// client to allow or deny quota is available.
mCallbackProxy.onExceededDatabaseQuota(url, databaseIdentifier,
- currentQuota, estimatedSize, getUsedQuota(),
+ quota, estimatedDatabaseSize, getUsedQuota(),
new WebStorage.QuotaUpdater() {
@Override
- public void updateQuota(long quota) {
- nativeSetNewStorageLimit(mNativeClass, quota);
+ public void updateQuota(long newQuota) {
+ nativeSetNewStorageLimit(mNativeClass, newQuota);
}
});
}
/**
* Notify the browser that the appcache has exceeded its max size.
- * @param spaceNeeded is the amount of disk space that would be needed
- * in order for the last appcache operation to succeed.
+ * @param requiredStorage is the amount of storage, in bytes, that would be
+ * needed in order for the last appcache operation to succeed.
*/
- protected void reachedMaxAppCacheSize(long spaceNeeded) {
- mCallbackProxy.onReachedMaxAppCacheSize(spaceNeeded, getUsedQuota(),
+ protected void reachedMaxAppCacheSize(long requiredStorage) {
+ mCallbackProxy.onReachedMaxAppCacheSize(requiredStorage, getUsedQuota(),
new WebStorage.QuotaUpdater() {
@Override
- public void updateQuota(long quota) {
- nativeSetNewStorageLimit(mNativeClass, quota);
+ public void updateQuota(long newQuota) {
+ nativeSetNewStorageLimit(mNativeClass, newQuota);
}
});
}
@@ -616,9 +618,6 @@
int unichar, int repeatCount, boolean isShift, boolean isAlt,
boolean isSym, boolean isDown);
- private native void nativeClick(int nativeClass, int framePtr, int nodePtr,
- boolean fake);
-
private native void nativeSendListBoxChoices(int nativeClass,
boolean[] choices, int size);
@@ -661,8 +660,6 @@
int x, int y);
private native String nativeRetrieveImageSource(int nativeClass,
int x, int y);
- private native void nativeTouchUp(int nativeClass,
- int touchGeneration, int framePtr, int nodePtr, int x, int y);
private native boolean nativeMouseClick(int nativeClass);
private native boolean nativeHandleTouchEvent(int nativeClass, int action,
@@ -1073,10 +1070,8 @@
static final int REPLACE_TEXT = 114;
static final int PASS_TO_JS = 115;
static final int SET_GLOBAL_BOUNDS = 116;
- static final int CLICK = 118;
static final int SET_NETWORK_STATE = 119;
static final int DOC_HAS_IMAGES = 120;
- static final int FAKE_CLICK = 121;
static final int DELETE_SELECTION = 122;
static final int LISTBOX_CHOICES = 123;
static final int SINGLE_LISTBOX_CHOICE = 124;
@@ -1145,7 +1140,7 @@
// accessibility support
static final int MODIFY_SELECTION = 190;
- static final int USE_MOCK_DEVICE_ORIENTATION = 191;
+ static final int SET_USE_MOCK_DEVICE_ORIENTATION = 191;
static final int AUTOFILL_FORM = 192;
@@ -1181,6 +1176,7 @@
// key was pressed (down and up)
static final int KEY_PRESS = 223;
+ static final int SET_INITIAL_FOCUS = 224;
// Private handler for WebCore messages.
private Handler mHandler;
@@ -1371,14 +1367,6 @@
keyPress(msg.arg1);
break;
- case FAKE_CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, true);
- break;
-
- case CLICK:
- nativeClick(mNativeClass, msg.arg1, msg.arg2, false);
- break;
-
case VIEW_SIZE_CHANGED: {
viewSizeChanged((WebViewClassic.ViewSizeData) msg.obj);
break;
@@ -1665,8 +1653,8 @@
.sendToTarget();
break;
- case USE_MOCK_DEVICE_ORIENTATION:
- useMockDeviceOrientation();
+ case SET_USE_MOCK_DEVICE_ORIENTATION:
+ setUseMockDeviceOrientation();
break;
case AUTOFILL_FORM:
@@ -1761,6 +1749,9 @@
WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget();
break;
}
+ case SET_INITIAL_FOCUS:
+ nativeSetInitialFocus(mNativeClass, msg.arg1);
+ break;
}
}
};
@@ -3008,8 +2999,8 @@
// TODO: Figure out what to do with this (b/6111818)
}
- private void useMockDeviceOrientation() {
- mDeviceMotionAndOrientationManager.useMock();
+ private void setUseMockDeviceOrientation() {
+ mDeviceMotionAndOrientationManager.setUseMock();
}
public void setMockDeviceOrientation(boolean canProvideAlpha, double alpha,
@@ -3084,6 +3075,7 @@
private native void nativeClearTextSelection(int nativeClass);
private native boolean nativeSelectWordAt(int nativeClass, int x, int y);
private native void nativeSelectAll(int nativeClass);
+ private native void nativeSetInitialFocus(int nativeClass, int keyDirection);
private static native void nativeCertTrustChanged();
}
diff --git a/core/java/android/webkit/WebViewInputDispatcher.java b/core/java/android/webkit/WebViewInputDispatcher.java
index e7024d9..0a0afaa 100644
--- a/core/java/android/webkit/WebViewInputDispatcher.java
+++ b/core/java/android/webkit/WebViewInputDispatcher.java
@@ -269,9 +269,8 @@
*/
public boolean postPointerEvent(MotionEvent event,
int webKitXOffset, int webKitYOffset, float webKitScale) {
- if (event == null
- || (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) {
- throw new IllegalArgumentException("event must be a pointer event");
+ if (event == null) {
+ throw new IllegalArgumentException("event cannot be null");
}
if (DEBUG) {
@@ -349,6 +348,12 @@
}
}
+ public boolean shouldShowTapHighlight() {
+ synchronized (mLock) {
+ return mPostLongPressScheduled || mPostClickScheduled;
+ }
+ }
+
private void postLongPress() {
synchronized (mLock) {
if (!mPostLongPressScheduled) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 057aabe..e68049c 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -2062,6 +2062,10 @@
child = mAdapter.getView(position, scrapView, this);
+ if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
position, getChildCount());
@@ -2082,6 +2086,11 @@
}
} else {
child = mAdapter.getView(position, null, this);
+
+ if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index efdfae3..f279f8e 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -191,6 +191,10 @@
if (view == null) {
// Make a new one
view = mAdapter.getView(selectedPosition, null, this);
+
+ if (view.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
if (view != null) {
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 97a864c..abfc577 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -24,14 +24,15 @@
import android.util.SparseArray;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-
/**
* An AdapterView is a view whose children are determined by an {@link Adapter}.
*
@@ -232,6 +233,11 @@
public AdapterView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
/**
@@ -643,6 +649,12 @@
public void setEmptyView(View emptyView) {
mEmptyView = emptyView;
+ // If not explicitly specified this view is important for accessibility.
+ if (emptyView != null
+ && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
final T adapter = getAdapter();
final boolean empty = ((adapter == null) || adapter.isEmpty());
updateEmptyStatus(empty);
@@ -846,12 +858,14 @@
}
} else {
fireOnSelected();
+ performAccessibilityActionsOnSelected();
}
}
}
void selectionChanged() {
- if (mOnItemSelectedListener != null) {
+ if (mOnItemSelectedListener != null
+ || AccessibilityManager.getInstance(mContext).isEnabled()) {
if (mInLayout || mBlockLayoutRequests) {
// If we are in a layout traversal, defer notification
// by posting. This ensures that the view tree is
@@ -863,20 +877,16 @@
post(mSelectionNotifier);
} else {
fireOnSelected();
+ performAccessibilityActionsOnSelected();
}
}
-
- // we fire selection events here not in View
- if (mSelectedPosition != ListView.INVALID_POSITION && isShown() && !isInTouchMode()) {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
- }
}
private void fireOnSelected() {
- if (mOnItemSelectedListener == null)
+ if (mOnItemSelectedListener == null) {
return;
-
- int selection = this.getSelectedItemPosition();
+ }
+ final int selection = getSelectedItemPosition();
if (selection >= 0) {
View v = getSelectedView();
mOnItemSelectedListener.onItemSelected(this, v, selection,
@@ -886,6 +896,17 @@
}
}
+ private void performAccessibilityActionsOnSelected() {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+ final int position = getSelectedItemPosition();
+ if (position >= 0) {
+ // we fire selection events here not in View
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
+ }
+ }
+
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
View selectedView = getSelectedView();
@@ -936,6 +957,24 @@
event.setItemCount(getCount());
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean onRequestAccessibilityFocusFromHover(float x, float y) {
+ // We prefer to five focus to the child instead of this view.
+ // Usually the children are not actionable for accessibility,
+ // and they will not take accessibility focus, so we give it.
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (isTransformedTouchPointInView(x, y, child, null)) {
+ return child.requestAccessibilityFocus();
+ }
+ }
+ return super.onRequestAccessibilityFocusFromHover(x, y);
+ }
+
private boolean isScrollableForAccessibility() {
T adapter = getAdapter();
if (adapter != null) {
@@ -1012,6 +1051,9 @@
mNeedSync = false;
checkSelectionChanged();
}
+
+ //TODO: Hmm, we do not know the old state so this is sub-optimal
+ notifyAccessibilityStateChanged();
}
void checkSelectionChanged() {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index bb00049..c557963 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -414,6 +414,10 @@
// get the fresh child from the adapter
final View updatedChild = mAdapter.getView(modulo(i, adapterCount), null, this);
+ if (updatedChild.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ updatedChild.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
if (mViewsMap.containsKey(index)) {
final FrameLayout fl = (FrameLayout) mViewsMap.get(index).view;
// add the new child to the frame, if it exists
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 0685eea..858c415 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -21,8 +21,6 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import com.android.internal.R;
-
/**
* <p>
@@ -71,16 +69,6 @@
}
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
- if (isChecked()) {
- event.getText().add(mContext.getString(R.string.checkbox_checked));
- } else {
- event.getText().add(mContext.getString(R.string.checkbox_not_checked));
- }
- }
-
- @Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(CheckBox.class.getName());
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index c5066b6..108b720 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -279,8 +279,13 @@
// re-order the number spinners to match the current date format
reorderSpinners();
- // set content descriptions
+ // accessibility
setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
/**
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index cbff58c..040a385 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1681,7 +1681,7 @@
final int itemCount = clipData.getItemCount();
for (int i=0; i < itemCount; i++) {
Item item = clipData.getItemAt(i);
- content.append(item.coerceToText(mTextView.getContext()));
+ content.append(item.coerceToStyledText(mTextView.getContext()));
}
final int offset = mTextView.getOffsetForPosition(event.getX(), event.getY());
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 60dd55c..1cb676f5 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -20,6 +20,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Insets;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
@@ -559,9 +560,9 @@
int flags = (gravity & mask) >> shift;
switch (flags) {
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE):
- return LEADING;
+ return horizontal ? LEFT : TOP;
case (AXIS_SPECIFIED | AXIS_PULL_AFTER):
- return TRAILING;
+ return horizontal ? RIGHT : BOTTOM;
case (AXIS_SPECIFIED | AXIS_PULL_BEFORE | AXIS_PULL_AFTER):
return FILL;
case AXIS_SPECIFIED:
@@ -1042,12 +1043,15 @@
int rightMargin = getMargin(c, true, false);
int bottomMargin = getMargin(c, false, false);
- // Alignment offsets: the location of the view relative to its alignment group.
- int alignmentOffsetX = boundsX.getOffset(c, hAlign, leftMargin + pWidth + rightMargin);
- int alignmentOffsetY = boundsY.getOffset(c, vAlign, topMargin + pHeight + bottomMargin);
+ int sumMarginsX = leftMargin + rightMargin;
+ int sumMarginsY = topMargin + bottomMargin;
- int width = hAlign.getSizeInCell(c, pWidth, cellWidth - leftMargin - rightMargin);
- int height = vAlign.getSizeInCell(c, pHeight, cellHeight - topMargin - bottomMargin);
+ // Alignment offsets: the location of the view relative to its alignment group.
+ int alignmentOffsetX = boundsX.getOffset(this, c, hAlign, pWidth + sumMarginsX, true);
+ int alignmentOffsetY = boundsY.getOffset(this, c, vAlign, pHeight + sumMarginsY, false);
+
+ int width = hAlign.getSizeInCell(c, pWidth, cellWidth - sumMarginsX);
+ int height = vAlign.getSizeInCell(c, pHeight, cellHeight - sumMarginsY);
int dx = x1 + gravityOffsetX + alignmentOffsetX;
@@ -1181,7 +1185,7 @@
View c = getChildAt(i);
LayoutParams lp = getLayoutParams(c);
Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
- groupBounds.getValue(i).include(c, spec, GridLayout.this, this);
+ groupBounds.getValue(i).include(GridLayout.this, c, spec, this);
}
}
@@ -2138,16 +2142,30 @@
return before + after;
}
- protected int getOffset(View c, Alignment alignment, int size) {
- return before - alignment.getAlignmentValue(c, size);
+ private int getAlignmentValue(GridLayout gl, View c, int size, Alignment a, boolean horiz) {
+ boolean useLayoutBounds = gl.getLayoutMode() == LAYOUT_BOUNDS;
+ if (!useLayoutBounds) {
+ return a.getAlignmentValue(c, size);
+ } else {
+ Insets insets = c.getLayoutInsets();
+ int leadingInset = horiz ? insets.left : insets.top; // RTL?
+ int trailingInset = horiz ? insets.right : insets.bottom; // RTL?
+ int totalInset = leadingInset + trailingInset;
+ return leadingInset + a.getAlignmentValue(c, size - totalInset);
+ }
}
- protected final void include(View c, Spec spec, GridLayout gridLayout, Axis axis) {
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean horizontal) {
+ return before - getAlignmentValue(gl, c, size, a, horizontal);
+ }
+
+ protected final void include(GridLayout gl, View c, Spec spec, Axis axis) {
this.flexibility &= spec.getFlexibility();
- int size = gridLayout.getMeasurementIncludingMargin(c, axis.horizontal);
- Alignment alignment = gridLayout.getAlignment(spec.alignment, axis.horizontal);
+ boolean horizontal = axis.horizontal;
+ int size = gl.getMeasurementIncludingMargin(c, horizontal);
+ Alignment alignment = gl.getAlignment(spec.alignment, horizontal);
// todo test this works correctly when the returned value is UNDEFINED
- int before = alignment.getAlignmentValue(c, size);
+ int before = getAlignmentValue(gl, c, size, alignment, horizontal);
include(before, size - before);
}
@@ -2614,8 +2632,8 @@
}
@Override
- protected int getOffset(View c, Alignment alignment, int size) {
- return max(0, super.getOffset(c, alignment, size));
+ protected int getOffset(GridLayout gl, View c, Alignment a, int size, boolean hrz) {
+ return max(0, super.getOffset(gl, c, a, size, hrz));
}
};
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 91e2e497..6c7ea67 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -105,11 +105,11 @@
super(context);
initImageView();
}
-
+
public ImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
-
+
public ImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initImageView();
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index d897a39..992849d 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -112,8 +112,7 @@
private static final int SELECTOR_ADJUSTMENT_DURATION_MILLIS = 800;
/**
- * The duration of scrolling to the next/previous value while snapping to
- * a given position.
+ * The duration of scrolling while snapping to a given position.
*/
private static final int SNAP_SCROLL_DURATION = 300;
@@ -579,7 +578,7 @@
throw new IllegalArgumentException("minWidth > maxWidth");
}
- mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
+ mComputeMaxWidth = (mMaxWidth == SIZE_UNSPECIFIED);
attributesArray.recycle();
@@ -680,6 +679,11 @@
mAdjustScroller = new Scroller(getContext(), new DecelerateInterpolator(2.5f));
updateInputTextView();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
@@ -771,6 +775,8 @@
mLastDownEventTime = event.getEventTime();
mIngonreMoveEvents = false;
mShowSoftInputOnTap = false;
+ // Make sure we wupport flinging inside scrollables.
+ getParent().requestDisallowInterceptTouchEvent(true);
if (!mFlingScroller.isFinished()) {
mFlingScroller.forceFinished(true);
mAdjustScroller.forceFinished(true);
@@ -1096,12 +1102,7 @@
* @see #setMaxValue(int)
*/
public void setValue(int value) {
- if (mValue == value) {
- return;
- }
setValueInternal(value, false);
- initializeSelectorWheelIndices();
- invalidate();
}
/**
@@ -1498,6 +1499,8 @@
if (notifyChange) {
notifyChange(previous, current);
}
+ initializeSelectorWheelIndices();
+ invalidate();
}
/**
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 22e9ef1..080b87d 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -80,16 +80,22 @@
/**
* Called when a share target has been selected. The client can
- * decide whether to handle the intent or rely on the default
- * behavior which is launching it.
+ * decide whether to perform some action before the sharing is
+ * actually performed.
* <p>
* <strong>Note:</strong> Modifying the intent is not permitted and
* any changes to the latter will be ignored.
* </p>
+ * <p>
+ * <strong>Note:</strong> You should <strong>not</strong> handle the
+ * intent here. This callback aims to notify the client that a
+ * sharing is being performed, so the client can update the UI
+ * if necessary.
+ * </p>
*
* @param source The source of the notification.
* @param intent The intent for launching the chosen share target.
- * @return Whether the client has handled the intent.
+ * @return The return result is ignored. Always return false for consistency.
*/
public boolean onShareTargetSelected(ShareActionProvider source, Intent intent);
}
@@ -308,7 +314,7 @@
@Override
public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
if (mOnShareTargetSelectedListener != null) {
- return mOnShareTargetSelectedListener.onShareTargetSelected(
+ mOnShareTargetSelectedListener.onShareTargetSelected(
ShareActionProvider.this, intent);
}
return false;
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index aef8a34..36d1ee0 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -907,7 +907,7 @@
public void onItemClick(AdapterView parent, View v, int position, long id) {
Spinner.this.setSelection(position);
if (mOnItemClickListener != null) {
- Spinner.this.performItemClick(null, position, mAdapter.getItemId(position));
+ Spinner.this.performItemClick(v, position, mAdapter.getItemId(position));
}
dismiss();
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index a897cc3..0786909 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -806,5 +806,16 @@
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setClassName(Switch.class.getName());
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = info.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ info.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ info.setText(newText);
+ }
+ }
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9867e47..37d9db7 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -1105,6 +1105,11 @@
setLongClickable(longClickable);
if (mEditor != null) mEditor.prepareCursorControllers();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -7710,7 +7715,7 @@
if (clip != null) {
boolean didFirst = false;
for (int i=0; i<clip.getItemCount(); i++) {
- CharSequence paste = clip.getItemAt(i).coerceToText(getContext());
+ CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
if (paste != null) {
if (!didFirst) {
long minMax = prepareSpacesAroundPaste(min, max, paste);
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index bc88b62..18f7a91 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -251,6 +251,11 @@
// set the content descriptions
setContentDescriptions();
+
+ // If not explicitly specified this view is important for accessibility.
+ if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index a0e125a..37567fd 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -29,9 +29,9 @@
import android.widget.Toast;
public class PlatLogoActivity extends Activity {
+ Vibrator mZzz;
Toast mToast;
ImageView mContent;
- Vibrator mZzz = new Vibrator();
int mCount;
final Handler mHandler = new Handler();
@@ -63,7 +63,8 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+
+ mZzz = (Vibrator)getSystemService(VIBRATOR_SERVICE);
mToast = Toast.makeText(this, "Android 4.0: Ice Cream Sandwich", Toast.LENGTH_SHORT);
mContent = new ImageView(this);
diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java
index 77d0c97..6a46929 100644
--- a/core/java/com/android/internal/app/ShutdownThread.java
+++ b/core/java/com/android/internal/app/ShutdownThread.java
@@ -37,6 +37,7 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Vibrator;
+import android.os.SystemVibrator;
import android.os.storage.IMountService;
import android.os.storage.IMountShutdownObserver;
@@ -399,7 +400,7 @@
}
} else if (SHUTDOWN_VIBRATE_MS > 0) {
// vibrate before shutting down
- Vibrator vibrator = new Vibrator();
+ Vibrator vibrator = new SystemVibrator();
try {
vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
} catch (Exception e) {
diff --git a/core/java/com/android/internal/util/ArrayUtils.java b/core/java/com/android/internal/util/ArrayUtils.java
index d1aa1ce..dbf6c8e 100644
--- a/core/java/com/android/internal/util/ArrayUtils.java
+++ b/core/java/com/android/internal/util/ArrayUtils.java
@@ -201,4 +201,40 @@
}
return array;
}
+
+ public static int[] appendInt(int[] cur, int val) {
+ if (cur == null) {
+ return new int[] { val };
+ }
+ final int N = cur.length;
+ for (int i = 0; i < N; i++) {
+ if (cur[i] == val) {
+ return cur;
+ }
+ }
+ int[] ret = new int[N + 1];
+ System.arraycopy(cur, 0, ret, 0, N);
+ ret[N] = val;
+ return ret;
+ }
+
+ public static int[] removeInt(int[] cur, int val) {
+ if (cur == null) {
+ return null;
+ }
+ final int N = cur.length;
+ for (int i = 0; i < N; i++) {
+ if (cur[i] == val) {
+ int[] ret = new int[N - 1];
+ if (i > 0) {
+ System.arraycopy(cur, 0, ret, 0, i);
+ }
+ if (i < (N - 1)) {
+ System.arraycopy(cur, i + 1, ret, i, N - i - 1);
+ }
+ return ret;
+ }
+ }
+ return cur;
+ }
}
diff --git a/core/java/com/android/internal/util/AsyncChannel.java b/core/java/com/android/internal/util/AsyncChannel.java
index 3973344..0c5d5ef 100644
--- a/core/java/com/android/internal/util/AsyncChannel.java
+++ b/core/java/com/android/internal/util/AsyncChannel.java
@@ -150,6 +150,24 @@
*/
public static final int CMD_CHANNEL_DISCONNECTED = BASE + 4;
+ private static final int CMD_TO_STRING_COUNT = CMD_CHANNEL_DISCONNECTED + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+ static {
+ sCmdToString[CMD_CHANNEL_HALF_CONNECTED - BASE] = "CMD_CHANNEL_HALF_CONNECTED";
+ sCmdToString[CMD_CHANNEL_FULL_CONNECTION - BASE] = "CMD_CHANNEL_FULL_CONNECTION";
+ sCmdToString[CMD_CHANNEL_FULLY_CONNECTED - BASE] = "CMD_CHANNEL_FULLY_CONNECTED";
+ sCmdToString[CMD_CHANNEL_DISCONNECT - BASE] = "CMD_CHANNEL_DISCONNECT";
+ sCmdToString[CMD_CHANNEL_DISCONNECTED - BASE] = "CMD_CHANNEL_DISCONNECTED";
+ }
+ protected static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return null;
+ }
+ }
+
/** Successful status always 0, !0 is an unsuccessful status */
public static final int STATUS_SUCCESSFUL = 0;
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 3dd2284..699e9b3 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -46,6 +46,10 @@
mCurrent = mBuilder.toString();
}
+ public void printPair(String key, Object value) {
+ print(key + "=" + String.valueOf(value) + " ");
+ }
+
@Override
public void println() {
super.println();
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 07496a7..1391ac3 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -20,9 +20,13 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.text.TextUtils;
import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.HashMap;
import java.util.Vector;
@@ -444,9 +448,11 @@
* The information maintained for a processed message.
*/
public static class ProcessedMessageInfo {
- private int what;
- private State state;
- private State orgState;
+ private long mTime;
+ private int mWhat;
+ private String mInfo;
+ private State mState;
+ private State mOrgState;
/**
* Constructor
@@ -455,8 +461,8 @@
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- ProcessedMessageInfo(Message message, State state, State orgState) {
- update(message, state, orgState);
+ ProcessedMessageInfo(Message msg, String info, State state, State orgState) {
+ update(msg, info, state, orgState);
}
/**
@@ -465,31 +471,47 @@
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- public void update(Message message, State state, State orgState) {
- this.what = message.what;
- this.state = state;
- this.orgState = orgState;
+ public void update(Message msg, String info, State state, State orgState) {
+ mTime = System.currentTimeMillis();
+ mWhat = msg.what;
+ mInfo = info;
+ mState = state;
+ mOrgState = orgState;
+ }
+
+ /**
+ * @return time stamp
+ */
+ public long getTime() {
+ return mTime;
+ }
+
+ /**
+ * @return msg.what
+ */
+ public long getWhat() {
+ return mWhat;
}
/**
* @return the command that was executing
*/
- public int getWhat() {
- return what;
+ public String getInfo() {
+ return mInfo;
}
/**
* @return the state that handled this message
*/
public State getState() {
- return state;
+ return mState;
}
/**
* @return the original state that received the message.
*/
public State getOriginalState() {
- return orgState;
+ return mOrgState;
}
/**
@@ -498,26 +520,24 @@
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
- sb.append("what=");
- sb.append(what);
+ sb.append("time=");
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(mTime);
+ sb.append(String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c));
sb.append(" state=");
- sb.append(cn(state));
+ sb.append(mState == null ? "<null>" : mState.getName());
sb.append(" orgState=");
- sb.append(cn(orgState));
- return sb.toString();
- }
-
- /**
- * @return an objects class name
- */
- private String cn(Object n) {
- if (n == null) {
- return "null";
- } else {
- String name = n.getClass().getName();
- int lastDollar = name.lastIndexOf('$');
- return name.substring(lastDollar + 1);
+ sb.append(mOrgState == null ? "<null>" : mOrgState.getName());
+ sb.append(" what=");
+ sb.append(mWhat);
+ sb.append("(0x");
+ sb.append(Integer.toHexString(mWhat));
+ sb.append(")");
+ if ( ! TextUtils.isEmpty(mInfo)) {
+ sb.append(" ");
+ sb.append(mInfo);
}
+ return sb.toString();
}
}
@@ -542,9 +562,9 @@
private int mCount = 0;
/**
- * Constructor
+ * private constructor use add
*/
- ProcessedMessages() {
+ private ProcessedMessages() {
}
/**
@@ -599,22 +619,23 @@
/**
* Add a processed message.
*
- * @param message
+ * @param msg
+ * @param messageInfo to be stored
* @param state that handled the message
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- void add(Message message, State state, State orgState) {
+ void add(Message msg, String messageInfo, State state, State orgState) {
mCount += 1;
if (mMessages.size() < mMaxSize) {
- mMessages.add(new ProcessedMessageInfo(message, state, orgState));
+ mMessages.add(new ProcessedMessageInfo(msg, messageInfo, state, orgState));
} else {
ProcessedMessageInfo pmi = mMessages.get(mOldestIndex);
mOldestIndex += 1;
if (mOldestIndex >= mMaxSize) {
mOldestIndex = 0;
}
- pmi.update(message, state, orgState);
+ pmi.update(msg, messageInfo, state, orgState);
}
}
}
@@ -894,11 +915,14 @@
/**
* Record that we processed the message
*/
- if (curStateInfo != null) {
- State orgState = mStateStack[mStateStackTopIndex].state;
- mProcessedMessages.add(msg, curStateInfo.state, orgState);
- } else {
- mProcessedMessages.add(msg, null, null);
+ if (mSm.recordProcessedMessage(msg)) {
+ if (curStateInfo != null) {
+ State orgState = mStateStack[mStateStackTopIndex].state;
+ mProcessedMessages.add(msg, mSm.getMessageInfo(msg), curStateInfo.state,
+ orgState);
+ } else {
+ mProcessedMessages.add(msg, mSm.getMessageInfo(msg), null, null);
+ }
}
}
@@ -1546,6 +1570,24 @@
}
/**
+ * @return true if msg should be saved in ProcessedMessage, default is true.
+ */
+ protected boolean recordProcessedMessage(Message msg) {
+ return true;
+ }
+
+ /**
+ * Return message info to be logged by ProcessedMessageInfo, default
+ * is an empty string. Override if additional information is desired.
+ *
+ * @param msg that was processed
+ * @return information to be logged as a String
+ */
+ protected String getMessageInfo(Message msg) {
+ return "";
+ }
+
+ /**
* @return if debugging is enabled
*/
public boolean isDbg() {
@@ -1577,4 +1619,21 @@
/** Send the complete construction message */
mSmHandler.completeConstruction();
}
+
+ /**
+ * Dump the current state.
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(getName() + ":");
+ pw.println(" total messages=" + getProcessedMessagesCount());
+ for (int i=0; i < getProcessedMessagesSize(); i++) {
+ pw.printf(" msg[%d]: %s\n", i, getProcessedMessageInfo(i));
+ pw.flush();
+ }
+ pw.println("curState=" + getCurrentState().getName());
+ }
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 8c05459..7a6f7d9 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -246,6 +246,10 @@
mHomeLayout.setOnClickListener(mUpClickListener);
mHomeLayout.setClickable(true);
mHomeLayout.setFocusable(true);
+
+ if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
@Override
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 0524c25..1d6af90 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -21,6 +21,8 @@
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetricsInt;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManager.InputDeviceListener;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -32,7 +34,7 @@
import java.util.ArrayList;
-public class PointerLocationView extends View {
+public class PointerLocationView extends View implements InputDeviceListener {
private static final String TAG = "Pointer";
public static class PointerState {
@@ -82,6 +84,8 @@
private final int ESTIMATE_FUTURE_POINTS = 2;
private final float ESTIMATE_INTERVAL = 0.02f;
+ private final InputManager mIm;
+
private final ViewConfiguration mVC;
private final Paint mTextPaint;
private final Paint mTextBackgroundPaint;
@@ -108,6 +112,8 @@
super(c);
setFocusableInTouchMode(true);
+ mIm = (InputManager)c.getSystemService(Context.INPUT_SERVICE);
+
mVC = ViewConfiguration.get(c);
mTextPaint = new Paint();
mTextPaint.setAntiAlias(true);
@@ -139,18 +145,6 @@
mActivePointerId = 0;
mVelocity = VelocityTracker.obtain();
-
- logInputDeviceCapabilities();
- }
-
- private void logInputDeviceCapabilities() {
- int[] deviceIds = InputDevice.getDeviceIds();
- for (int i = 0; i < deviceIds.length; i++) {
- InputDevice device = InputDevice.getDevice(deviceIds[i]);
- if (device != null) {
- Log.i(TAG, device.toString());
- }
- }
}
public void setPrintCoords(boolean state) {
@@ -631,7 +625,53 @@
logMotionEvent("Trackball", event);
return true;
}
-
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ mIm.registerInputDeviceListener(this, getHandler());
+ logInputDevices();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mIm.unregisterInputDeviceListener(this);
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ logInputDeviceState(deviceId, "Device Added");
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ logInputDeviceState(deviceId, "Device Changed");
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ logInputDeviceState(deviceId, "Device Removed");
+ }
+
+ private void logInputDevices() {
+ int[] deviceIds = InputDevice.getDeviceIds();
+ for (int i = 0; i < deviceIds.length; i++) {
+ logInputDeviceState(deviceIds[i], "Device Enumerated");
+ }
+ }
+
+ private void logInputDeviceState(int deviceId, String state) {
+ InputDevice device = mIm.getInputDevice(deviceId);
+ if (device != null) {
+ Log.i(TAG, state + ": " + device);
+ } else {
+ Log.i(TAG, state + ": " + deviceId);
+ }
+ }
+
// HACK
// A quick and dirty string builder implementation optimized for GC.
// Using String.format causes the application grind to a halt when
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 879b9d2..b877071 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -549,6 +549,10 @@
opt.optionString = heapsizeOptsBuf;
mOptions.add(opt);
+ // Increase the main thread's interpreter stack size for bug 6315322.
+ opt.optionString = "-XX:mainThreadStackSize=24K";
+ mOptions.add(opt);
+
strcpy(heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
property_get("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf+20, "");
if (heapgrowthlimitOptsBuf[20] != '\0') {
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 376c841..a65262c 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -254,6 +254,13 @@
obj->setTextAlign(align);
}
+ static void setTextLocale(JNIEnv* env, jobject clazz, SkPaint* obj, jstring locale) {
+ const char* localeArray = env->GetStringUTFChars(locale, NULL);
+ SkString skLocale(localeArray);
+ obj->setTextLocale(skLocale);
+ env->ReleaseStringUTFChars(locale, localeArray);
+ }
+
static jfloat getTextSize(JNIEnv* env, jobject paint) {
NPE_CHECK_RETURN_ZERO(env, paint);
return SkScalarToFloat(GraphicsJNI::getNativePaint(env, paint)->getTextSize());
@@ -817,6 +824,7 @@
{"native_setRasterizer","(II)I", (void*) SkPaintGlue::setRasterizer},
{"native_getTextAlign","(I)I", (void*) SkPaintGlue::getTextAlign},
{"native_setTextAlign","(II)V", (void*) SkPaintGlue::setTextAlign},
+ {"native_setTextLocale","(ILjava/lang/String;)V", (void*) SkPaintGlue::setTextLocale},
{"getTextSize","()F", (void*) SkPaintGlue::getTextSize},
{"setTextSize","(F)V", (void*) SkPaintGlue::setTextSize},
{"getTextScaleX","()F", (void*) SkPaintGlue::getTextScaleX},
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 244b166..c48b974 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -35,6 +35,7 @@
static const char* const OutOfResourcesException =
"android/graphics/SurfaceTexture$OutOfResourcesException";
+static const char* const IllegalStateException = "java/lang/IllegalStateException";
const char* const kSurfaceTextureClassPathName = "android/graphics/SurfaceTexture";
struct fields_t {
@@ -212,10 +213,16 @@
surfaceTexture->setDefaultBufferSize(width, height);
}
-static jint SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz)
+static void SurfaceTexture_updateTexImage(JNIEnv* env, jobject thiz)
{
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
- return surfaceTexture->updateTexImage();
+ status_t err = surfaceTexture->updateTexImage();
+ if (err == INVALID_OPERATION) {
+ jniThrowException(env, IllegalStateException, "Unable to update texture contents (see "
+ "logcat for details)");
+ } else if (err < 0) {
+ jniThrowRuntimeException(env, "Error during updateTexImage (see logcat for details)");
+ }
}
static jint SurfaceTexture_detachFromGLContext(JNIEnv* env, jobject thiz)
@@ -258,7 +265,7 @@
{"nativeInit", "(ILjava/lang/Object;Z)V", (void*)SurfaceTexture_init },
{"nativeFinalize", "()V", (void*)SurfaceTexture_finalize },
{"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
- {"nativeUpdateTexImage", "()I", (void*)SurfaceTexture_updateTexImage },
+ {"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage },
{"nativeDetachFromGLContext", "()I", (void*)SurfaceTexture_detachFromGLContext },
{"nativeAttachToGLContext", "(I)I", (void*)SurfaceTexture_attachToGLContext },
{"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix },
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index a7abbfa..a97c710 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -29,10 +29,7 @@
namespace android {
//--------------------------------------------------------------------------------------------------
-// Using DroidSansArabic for shaping Arabic with Harfbuzz because its metrics are more compatible
-// with the "Roboto" metrics (compared to DroidNaskh-Regular). When we will have an Arabic font
-// whose metrics are similar to the Roboto ones, then we will need to use it for shaping.
-#define TYPEFACE_ARABIC "/system/fonts/DroidSansArabic.ttf"
+#define TYPEFACE_ARABIC "/system/fonts/DroidNaskh-Regular-Shift.ttf"
#define TYPE_FACE_HEBREW_REGULAR "/system/fonts/DroidSansHebrew-Regular.ttf"
#define TYPE_FACE_HEBREW_BOLD "/system/fonts/DroidSansHebrew-Bold.ttf"
#define TYPEFACE_BENGALI "/system/fonts/Lohit-Bengali.ttf"
diff --git a/core/jni/android_net_TrafficStats.cpp b/core/jni/android_net_TrafficStats.cpp
index 0ab659b..325fe26 100644
--- a/core/jni/android_net_TrafficStats.cpp
+++ b/core/jni/android_net_TrafficStats.cpp
@@ -31,6 +31,9 @@
namespace android {
+static const uint64_t VALUE_UNKNOWN = -1;
+static const char* IFACE_STAT_ALL = "/proc/net/xt_qtaguid/iface_stat_all";
+
enum Tx_Rx {
TX,
RX
@@ -42,6 +45,21 @@
TCP_AND_UDP
};
+// NOTE: keep these in sync with TrafficStats.java
+enum IfaceStatType {
+ RX_BYTES = 0,
+ RX_PACKETS = 1,
+ TX_BYTES = 2,
+ TX_PACKETS = 3
+};
+
+struct IfaceStat {
+ uint64_t rxBytes;
+ uint64_t rxPackets;
+ uint64_t txBytes;
+ uint64_t txPackets;
+};
+
// Returns an ASCII decimal number read from the specified file, -1 on error.
static jlong readNumber(char const* filename) {
char buf[80];
@@ -63,130 +81,82 @@
return atoll(buf);
}
-static const char* mobile_iface_list[] = {
- "rmnet0",
- "rmnet1",
- "rmnet2",
- "rmnet3",
- "cdma_rmnet4",
- "ppp0",
- 0
-};
+static int parseIfaceStat(const char* iface, struct IfaceStat* stat) {
+ FILE *fp = fopen(IFACE_STAT_ALL, "r");
+ if (!fp) {
+ return errno;
+ }
-static jlong getAll(const char** iface_list, const char* what) {
+ char buffer[256];
+ char cur_iface[32];
+ int active;
+ uint64_t rxBytes, rxPackets, txBytes, txPackets, devRxBytes, devRxPackets, devTxBytes,
+ devTxPackets;
- char filename[80];
- int idx = 0;
- bool supported = false;
- jlong total = 0;
- while (iface_list[idx] != 0) {
-
- snprintf(filename, sizeof(filename), "/sys/class/net/%s/statistics/%s",
- iface_list[idx], what);
- jlong number = readNumber(filename);
- if (number >= 0) {
- supported = true;
- total += number;
+ while (fgets(buffer, 256, fp) != NULL) {
+ if (sscanf(buffer, "%31s %d %llu %llu %llu %llu %llu %llu %llu %llu", cur_iface, &active,
+ &rxBytes, &rxPackets, &txBytes, &txPackets, &devRxBytes, &devRxPackets,
+ &devTxBytes, &devTxPackets) != 10) {
+ continue;
}
- idx++;
- }
- if (supported) return total;
- return -1;
-}
+ if (!iface || !strcmp(iface, cur_iface)) {
+ stat->rxBytes += rxBytes;
+ stat->rxPackets += rxPackets;
+ stat->txBytes += txBytes;
+ stat->txPackets += txPackets;
-// Returns the sum of numbers from the specified path under /sys/class/net/*,
-// -1 if no such file exists.
-static jlong readTotal(char const* suffix) {
- char filename[PATH_MAX] = "/sys/class/net/";
- DIR *dir = opendir(filename);
- if (dir == NULL) {
- ALOGE("Can't list %s: %s", filename, strerror(errno));
- return -1;
- }
-
- int len = strlen(filename);
- jlong total = -1;
- while (struct dirent *entry = readdir(dir)) {
- // Skip ., .., and localhost interfaces.
- if (entry->d_name[0] != '.' && strncmp(entry->d_name, "lo", 2) != 0) {
- strlcpy(filename + len, entry->d_name, sizeof(filename) - len);
- strlcat(filename, suffix, sizeof(filename));
- jlong num = readNumber(filename);
- if (num >= 0) total = total < 0 ? num : total + num;
+ if (active) {
+ stat->rxBytes += devRxBytes;
+ stat->rxPackets += devRxPackets;
+ stat->txBytes += devTxBytes;
+ stat->txPackets += devTxPackets;
+ }
}
}
- closedir(dir);
- return total;
+ fclose(fp);
+ return 0;
}
-// Mobile stats get accessed a lot more often than total stats.
-// Note the individual files can come and go at runtime, so we check
-// each file every time (rather than caching which ones exist).
+static uint64_t getIfaceStatType(const char* iface, IfaceStatType type) {
+ struct IfaceStat stat;
+ memset(&stat, 0, sizeof(IfaceStat));
-static jlong getMobileTxPackets(JNIEnv* env, jobject clazz) {
- return getAll(mobile_iface_list, "tx_packets");
-}
-
-static jlong getMobileRxPackets(JNIEnv* env, jobject clazz) {
- return getAll(mobile_iface_list, "rx_packets");
-}
-
-static jlong getMobileTxBytes(JNIEnv* env, jobject clazz) {
- return getAll(mobile_iface_list, "tx_bytes");
-}
-
-static jlong getMobileRxBytes(JNIEnv* env, jobject clazz) {
- return getAll(mobile_iface_list, "rx_bytes");
-}
-
-static jlong getData(JNIEnv* env, const char* what, jstring javaInterface) {
- ScopedUtfChars interface(env, javaInterface);
- if (interface.c_str() == NULL) {
- return -1;
+ if (parseIfaceStat(iface, &stat)) {
+ return VALUE_UNKNOWN;
}
- char filename[80];
- snprintf(filename, sizeof(filename), "/sys/class/net/%s/statistics/%s", interface.c_str(), what);
- return readNumber(filename);
+ switch (type) {
+ case RX_BYTES:
+ return stat.rxBytes;
+ case RX_PACKETS:
+ return stat.rxPackets;
+ case TX_BYTES:
+ return stat.txBytes;
+ case TX_PACKETS:
+ return stat.txPackets;
+ default:
+ return VALUE_UNKNOWN;
+ }
}
-static jlong getTxPackets(JNIEnv* env, jobject clazz, jstring interface) {
- return getData(env, "tx_packets", interface);
+static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type) {
+ return getIfaceStatType(NULL, (IfaceStatType) type);
}
-static jlong getRxPackets(JNIEnv* env, jobject clazz, jstring interface) {
- return getData(env, "rx_packets", interface);
+static jlong getIfaceStat(JNIEnv* env, jclass clazz, jstring iface, jint type) {
+ struct IfaceStat stat;
+ const char* ifaceChars = env->GetStringUTFChars(iface, NULL);
+ if (ifaceChars) {
+ uint64_t stat = getIfaceStatType(ifaceChars, (IfaceStatType) type);
+ env->ReleaseStringUTFChars(iface, ifaceChars);
+ return stat;
+ } else {
+ return VALUE_UNKNOWN;
+ }
}
-static jlong getTxBytes(JNIEnv* env, jobject clazz, jstring interface) {
- return getData(env, "tx_bytes", interface);
-}
-
-static jlong getRxBytes(JNIEnv* env, jobject clazz, jstring interface) {
- return getData(env, "rx_bytes", interface);
-}
-
-
-// Total stats are read less often, so we're willing to put up
-// with listing the directory and concatenating filenames.
-
-static jlong getTotalTxPackets(JNIEnv* env, jobject clazz) {
- return readTotal("/statistics/tx_packets");
-}
-
-static jlong getTotalRxPackets(JNIEnv* env, jobject clazz) {
- return readTotal("/statistics/rx_packets");
-}
-
-static jlong getTotalTxBytes(JNIEnv* env, jobject clazz) {
- return readTotal("/statistics/tx_bytes");
-}
-
-static jlong getTotalRxBytes(JNIEnv* env, jobject clazz) {
- return readTotal("/statistics/rx_bytes");
-}
// Per-UID stats require reading from a constructed filename.
@@ -323,18 +293,8 @@
}
static JNINativeMethod gMethods[] = {
- {"getMobileTxPackets", "()J", (void*) getMobileTxPackets},
- {"getMobileRxPackets", "()J", (void*) getMobileRxPackets},
- {"getMobileTxBytes", "()J", (void*) getMobileTxBytes},
- {"getMobileRxBytes", "()J", (void*) getMobileRxBytes},
- {"getTxPackets", "(Ljava/lang/String;)J", (void*) getTxPackets},
- {"getRxPackets", "(Ljava/lang/String;)J", (void*) getRxPackets},
- {"getTxBytes", "(Ljava/lang/String;)J", (void*) getTxBytes},
- {"getRxBytes", "(Ljava/lang/String;)J", (void*) getRxBytes},
- {"getTotalTxPackets", "()J", (void*) getTotalTxPackets},
- {"getTotalRxPackets", "()J", (void*) getTotalRxPackets},
- {"getTotalTxBytes", "()J", (void*) getTotalTxBytes},
- {"getTotalRxBytes", "()J", (void*) getTotalRxBytes},
+ {"nativeGetTotalStat", "(I)J", (void*) getTotalStat},
+ {"nativeGetIfaceStat", "(Ljava/lang/String;I)J", (void*) getIfaceStat},
/* Per-UID Stats */
{"getUidTxBytes", "(I)J", (void*) getUidTxBytes},
diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp
index c5ff16e..e7c4c23 100644
--- a/core/jni/android_net_wifi_Wifi.cpp
+++ b/core/jni/android_net_wifi_Wifi.cpp
@@ -27,6 +27,7 @@
#define WIFI_PKG_NAME "android/net/wifi/WifiNative"
#define BUF_SIZE 256
+#define EVENT_BUF_SIZE 2048
namespace android {
@@ -140,7 +141,7 @@
static jstring android_net_wifi_waitForEvent(JNIEnv* env, jobject, jstring jIface)
{
- char buf[BUF_SIZE];
+ char buf[EVENT_BUF_SIZE];
ScopedUtfChars ifname(env, jIface);
int nread = ::wifi_wait_for_event(ifname.c_str(), buf, sizeof buf);
if (nread > 0) {
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index d7d476a..5cb172b 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -54,9 +54,10 @@
}
ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz,
- gInputDeviceClassInfo.ctor, deviceInfo.getId(), nameObj.get(),
- descriptorObj.get(), deviceInfo.getSources(), deviceInfo.getKeyboardType(),
- kcmObj.get()));
+ gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
+ nameObj.get(), descriptorObj.get(),
+ deviceInfo.getSources(), deviceInfo.getKeyboardType(),
+ kcmObj.get(), deviceInfo.hasVibrator()));
const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (size_t i = 0; i < ranges.size(); i++) {
@@ -86,7 +87,7 @@
gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
- "<init>", "(ILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
+ "<init>", "(IILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;Z)V");
GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFF)V");
diff --git a/core/res/res/anim/lock_screen_behind_enter.xml b/core/res/res/anim/lock_screen_behind_enter.xml
index 4f58be4..78b7d29 100644
--- a/core/res/res/anim/lock_screen_behind_enter.xml
+++ b/core/res/res/anim/lock_screen_behind_enter.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
-** Copyright 2007, The Android Open Source Project
+** Copyright 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.
@@ -18,10 +18,10 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:detachWallpaper="true" android:shareInterpolator="false">
+ android:background="#ff000000" android:shareInterpolator="false">
<scale
- android:fromXScale="0.95" android:toXScale="1.0"
- android:fromYScale="0.95" android:toYScale="1.0"
+ android:fromXScale="0.90" android:toXScale="1.0"
+ android:fromYScale="0.90" android:toYScale="1.0"
android:pivotX="50%p" android:pivotY="50%p"
android:fillEnabled="true" android:fillBefore="true"
android:interpolator="@interpolator/decelerate_cubic"
@@ -30,7 +30,7 @@
<alpha
android:fromAlpha="0.0" android:toAlpha="1.0"
android:fillEnabled="true" android:fillBefore="true"
- android:interpolator="@interpolator/decelerate_quad"
+ android:interpolator="@interpolator/decelerate_quint"
android:startOffset="@android:integer/config_shortAnimTime"
android:duration="@android:integer/config_shortAnimTime"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
new file mode 100644
index 0000000..a354fae
--- /dev/null
+++ b/core/res/res/anim/lock_screen_wallpaper_behind_enter.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:detachWallpaper="true" android:shareInterpolator="false">
+ <scale
+ android:fromXScale="0.95" android:toXScale="1.0"
+ android:fromYScale="0.95" android:toYScale="1.0"
+ android:pivotX="50%p" android:pivotY="50%p"
+ android:fillEnabled="true" android:fillBefore="true"
+ android:interpolator="@interpolator/decelerate_cubic"
+ android:startOffset="@android:integer/config_shortAnimTime"
+ android:duration="@android:integer/config_shortAnimTime" />
+ <alpha
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:fillEnabled="true" android:fillBefore="true"
+ android:interpolator="@interpolator/decelerate_quad"
+ android:startOffset="@android:integer/config_shortAnimTime"
+ android:duration="@android:integer/config_shortAnimTime"/>
+</set>
diff --git a/core/res/res/anim/screen_rotate_180_enter.xml b/core/res/res/anim/screen_rotate_180_enter.xml
index e2f3ce2..688a8d5 100644
--- a/core/res/res/anim/screen_rotate_180_enter.xml
+++ b/core/res/res/anim/screen_rotate_180_enter.xml
@@ -24,5 +24,5 @@
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_longAnimTime" />
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_180_exit.xml b/core/res/res/anim/screen_rotate_180_exit.xml
index fe4a950..1eb6361 100644
--- a/core/res/res/anim/screen_rotate_180_exit.xml
+++ b/core/res/res/anim/screen_rotate_180_exit.xml
@@ -24,5 +24,8 @@
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_longAnimTime" />
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_cubic"
+ android:duration="@android:integer/config_mediumAnimTime"/>
</set>
\ No newline at end of file
diff --git a/core/res/res/anim/screen_rotate_180_frame.xml b/core/res/res/anim/screen_rotate_180_frame.xml
index 1a3ee67..19dade1 100644
--- a/core/res/res/anim/screen_rotate_180_frame.xml
+++ b/core/res/res/anim/screen_rotate_180_frame.xml
@@ -24,5 +24,5 @@
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
- android:duration="@android:integer/config_longAnimTime" />
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_enter.xml b/core/res/res/anim/screen_rotate_minus_90_enter.xml
index 38a674d..b16d5fc 100644
--- a/core/res/res/anim/screen_rotate_minus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_enter.xml
@@ -19,10 +19,18 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!-- Version for two-phase anim
<rotate android:fromDegrees="-90" android:toDegrees="0"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <rotate android:fromDegrees="-90" android:toDegrees="0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_minus_90_exit.xml b/core/res/res/anim/screen_rotate_minus_90_exit.xml
index a75aca7..9b38939 100644
--- a/core/res/res/anim/screen_rotate_minus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_minus_90_exit.xml
@@ -19,10 +19,30 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="0" android:toDegrees="90"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <rotate android:fromDegrees="0" android:toDegrees="90"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_enter.xml b/core/res/res/anim/screen_rotate_plus_90_enter.xml
index 583d2ba..86a8d24 100644
--- a/core/res/res/anim/screen_rotate_plus_90_enter.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_enter.xml
@@ -19,10 +19,18 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="90" android:toDegrees="0"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <rotate android:fromDegrees="90" android:toDegrees="0"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/anim/screen_rotate_plus_90_exit.xml b/core/res/res/anim/screen_rotate_plus_90_exit.xml
index a2bef41..fa34533 100644
--- a/core/res/res/anim/screen_rotate_plus_90_exit.xml
+++ b/core/res/res/anim/screen_rotate_plus_90_exit.xml
@@ -19,10 +19,30 @@
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
+ <!-- Version for two-phase animation
<rotate android:fromDegrees="0" android:toDegrees="-90"
android:pivotX="50%" android:pivotY="50%"
android:interpolator="@interpolator/decelerate_quint"
android:fillEnabled="true"
android:fillBefore="true" android:fillAfter="true"
android:duration="@android:integer/config_longAnimTime" />
+ -->
+ <scale android:fromXScale="100%" android:toXScale="100%p"
+ android:fromYScale="100%" android:toYScale="100%p"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <rotate android:fromDegrees="0" android:toDegrees="-90"
+ android:pivotX="50%" android:pivotY="50%"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:interpolator="@interpolator/decelerate_quint"
+ android:fillEnabled="true"
+ android:fillBefore="true" android:fillAfter="true"
+ android:duration="@android:integer/config_mediumAnimTime" />
</set>
diff --git a/core/res/res/drawable-nodpi/view_accessibility_focused.9.png b/core/res/res/drawable-nodpi/view_accessibility_focused.9.png
new file mode 100644
index 0000000..f03f575
--- /dev/null
+++ b/core/res/res/drawable-nodpi/view_accessibility_focused.9.png
Binary files differ
diff --git a/core/res/res/raw/accessibility_gestures.bin b/core/res/res/raw/accessibility_gestures.bin
new file mode 100644
index 0000000..1f95e56
--- /dev/null
+++ b/core/res/res/raw/accessibility_gestures.bin
Binary files differ
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 9528a7a..d299436 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Laat die program toe om die skerm se rotasie te eniger tyd te verander. Dit moet nooit vir normale programme nodig wees nie."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"verander wyserspoed"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Laat die program toe om die muis of stuurpaneel se wyserspoed te eniger tyd te verander. Dit moet nooit vir normale programme nodig wees nie."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"verander sleutelborduitleg"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Laat die program toe om die uitleg van die sleutelbord te verander. Behoort nooit vir gewone programme nodig te wees nie."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"stuur Linux-seine na programme"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Laat die program toe om te versoek dat die voorsiende sein na alle aanhoudende prosesse gestuur word."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"laat program altyd loop"</string>
@@ -1009,11 +1007,17 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer die vereiste PIN in:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Voeg karakter in"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekend program"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Stuur SMS-boodskappe"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"\'n Groot aantal SMS-boodskappe word gestuur. Raak OK om voort te gaan, of Kanselleer om op te hou stuur."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Kanselleer"</string>
+ <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> stuur \'n groot aantal SMS-boodskappe. Wil jy hierdie program toelaat om voort te gaan om boodskappe te stuur?"</string>
+ <string name="sms_control_yes" msgid="3663725993855816807">"Laat toe"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"Weier"</string>
+ <string name="sms_short_code_confirm_title" msgid="1666863092640877318">"Stuur SMS na kortkode?"</string>
+ <string name="sms_premium_short_code_confirm_title" msgid="3811263856304367838">"Stuur \'n premium SMS?"</string>
+ <string name="sms_short_code_confirm_message" msgid="5616409294907295407">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> wil graag \'n SMS stuur aan <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, wat lyk asof dit \'n SMS-kortkode is.<p>Die stuur van SMS\'e na sommige kortkodes kan veroorsaak dat jou selfoonrekening gedebiteer word vir premium dienste.<p>Wil jy hierdie program toelaat om die boodskap te stuur?"</string>
+ <string name="sms_premium_short_code_confirm_message" msgid="6214083016284738667">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> wil graag \'n SMS stuur aan <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, wat \'n betaalde SMS-kortkode is.<p><b>As jy \'n boodskap na hierdie bestemming stuur, sal jou selfoonrekening gedebiteer word vir betaalde dienste.</b><p>Wil jy hierdie program toelaat om die boodskap te stuur?"</string>
+ <string name="sms_short_code_confirm_allow" msgid="8957573662645722940">"Stuur boodskap"</string>
+ <string name="sms_short_code_confirm_deny" msgid="6374609298084435887">"Moenie stuur nie"</string>
+ <string name="sms_short_code_confirm_report" msgid="2588793956061677070">"Gee kwaadwillige program aan"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kaart verwyder"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Die mobielnetwerk sal nie beskikbaar wees nie totdat jy weer begin met \'n geldige SIM-kaart."</string>
<string name="sim_done_button" msgid="827949989369963775">"Klaar"</string>
@@ -1063,10 +1067,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Raak om USB-ontfouting te deaktiveer."</string>
<string name="select_input_method" msgid="4653387336791222978">"Kies invoermetode"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Stel invoermetodes op"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fisiese sleutelbord"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardeware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidate"</u></string>
@@ -1193,14 +1195,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Verminder dag"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Vermeerder jaar"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Verminder jaar"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"gekontroleer"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nie gekontroleer nie"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"gekies"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nie gekies nie"</string>
- <string name="switch_on" msgid="551417728476977311">"aan"</string>
- <string name="switch_off" msgid="7249798614327155088">"af"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"gedruk"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nie gedruk nie"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Kanselleer"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Vee uit"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 747b12d..b2714dd 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"በማንኛውም ጊዜ የማሳያውን መሽከርከር ለመለወጥ ለመተግበሪያው ይፈቅዳሉ፡፡ ለተለመዱ መተግበሪያዎች አያስፈልግም፡፡"</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"የጠቋሚ ፍጥነት ለውጥ"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"መዳፊት ወይም ዱካ መከተያ ጠቋሚ ፍጥነትን በማንኛውም ጊዜ ለመለወጥ ለመተግበሪያው ይፈቅዳሉ፡፡ ለመደበኛ መተግበሪያዎች መቼም ቢሆን አያስፈልግም፡፡"</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"የቁልፍ ሰሌዳ አቀማመጥ ቀይር"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"መተግበሪያው የቁልፍ ሰሌዳ አቀማመጡን እንዲቀይር ይፈቅድለታል። ለመደበኛ መተግበሪያዎች መቼም ቢሆን ሊያስፈልግ አይገባም።"</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"ወደ መተግበሪያዎችን የLinux ምልክቶች ላክ"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"ለሁሉም ተከታታይ ሂደቶች ልከው የሚያቀርቧቸው ሲግናሎችን ለመጠየቅ ለመተግበሪያው ይፈቅዳሉ።"</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"ትግበራ ሁልጊዜ አሂድ ላይ አድርግ"</string>
@@ -1009,11 +1007,17 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"የሚፈለገውን ፒን ተይብ፦"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ፒን፦"</string>
<string name="select_character" msgid="3365550120617701745">"ቁምፊ አስገባ"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"ያልታወቀ መተግበሪያ"</string>
<string name="sms_control_title" msgid="7296612781128917719">"የSMS መልዕክቶች መበላክ ላይ"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"በጣም ብዙ የ SMS መልዕክቶቸ ተልከዋል።ለመቀጠል \"እሺ \" ፣ ወይም መላክ ለማቆም\"ተው \" ምረጥ።"</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"እሺ"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"ይቅር"</string>
+ <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ቁጥሩ ብዙ የሆኑ የኤስ.ኤም.ኤስ. መልዕቶችን እየላከ ነው። ይሄ መተግበሪያ መልዕክቶችን መላኩን እንዲቀጥል መፍቀድ ትፈልጋለህ?"</string>
+ <string name="sms_control_yes" msgid="3663725993855816807">"ፍቀድ"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"ከልክል"</string>
+ <string name="sms_short_code_confirm_title" msgid="1666863092640877318">"ኤስ.ኤም.ኤስ. ለአጭር ኮድ ይላክ?"</string>
+ <string name="sms_premium_short_code_confirm_title" msgid="3811263856304367838">"ከፍ ያለ ኤስ.ኤም.ኤስ. ይላክ?"</string>
+ <string name="sms_short_code_confirm_message" msgid="5616409294907295407">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> የጽሑፍ መልዕክት ለ<b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b> መላክ ይፈልጋል፣ ይሄ ደግሞ የኤስ.ኤም.ኤስ. አጭር ኮድ ሆኖ ተገኝቷል።<p>የጽሑፍ መልዕክቶች ለሆኑ አጭር ኮዶች መላክ የተንቀሳቃሽ መለያህ ከፍ ላሉ አገልግሎቶች ሊያስከፍለው ይችላል።<p>ይሄ መተግበሪያ መልዕክቱ እንዲልክ መፍቀድ ትፈልጋለህ?"</string>
+ <string name="sms_premium_short_code_confirm_message" msgid="6214083016284738667">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> የጽሑፍ መልዕክት ለ<b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b> መላክ ይፈልጋል፣ ይሄ ደግሞ ከፍ ያለ የኤስ.ኤም.ኤስ. አጭር ኮድ ነው።<p><b>መልዕክት ወደዚህ ቦታ መላክ የተንቀሳቃሽ መለያህ ከፍ ላሉ አገልግሎቶች ያስከፍለዋል።</b><p>ይሄ መተግበሪያ መልዕክቱ እንዲልክ መፍቀድ ትፈልጋለህ?"</string>
+ <string name="sms_short_code_confirm_allow" msgid="8957573662645722940">"መልዕክት ላክ"</string>
+ <string name="sms_short_code_confirm_deny" msgid="6374609298084435887">"አትላክ"</string>
+ <string name="sms_short_code_confirm_report" msgid="2588793956061677070">"ተንኮል አዘል መተግበሪያ ሪፖርት አድርግ"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"SIM ካርድ ተወግዷል"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"በትክክል የገባ SIM ካርድ ድጋሚ እስኪያስጀምሩ የተንቀሳቃሽ ስልክ አውታረመረብ አይገኝም።"</string>
<string name="sim_done_button" msgid="827949989369963775">"ተከናውኗል"</string>
@@ -1063,10 +1067,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"USB ማረሚያ ላለማንቃት ዳስስ።"</string>
<string name="select_input_method" msgid="4653387336791222978">"የግቤት ስልት ምረጥ"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"የግቤት ስልቶችን አዘጋጅ"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"የሚዳሰስ የቁልፍ ሰሌዳ"</string>
+ <string name="hardware" msgid="7517821086888990278">"ሃርድዌር"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"ዕጩዎች"</u></string>
@@ -1193,14 +1195,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"ቀን ቀንስ"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"ዓመት ጨምር"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"ዓመት ቀንስ"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"ታይቷል"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"አልተፈተሸም"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"የተመረጠ"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"አልተመረጠም"</string>
- <string name="switch_on" msgid="551417728476977311">"በ:"</string>
- <string name="switch_off" msgid="7249798614327155088">"ውጪ"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"ተጭኗል"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"አልተጫነም።"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"ተወው"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"ሰርዝ"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index e95ea69..daef582 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"للسماح للتطبيق بتغيير تدوير الشاشة في أي وقت. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"تغيير سرعة المؤشر"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"للسماح للتطبيق بتغيير سرعة مؤشر الماوس أو لوحة التتبع في أي وقت. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"تغيير تنسيق لوحة مفاتيح"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"للسماح للتطبيق بتغيير تنسيق لوحة المفاتيح. لن تكون هناك حاجة إليه مطلقًا مع التطبيقات العادية."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"إرسال إشارات Linux للتطبيقات"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"للسماح للتطبيق بطلب إرسال الإشارة المزوّدة لجميع العمليات المستمرة."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"تشغيل التطبيق دائمًا"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"اكتب رقم التعريف الشخصي المطلوب:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"رقم التعريف الشخصي:"</string>
<string name="select_character" msgid="3365550120617701745">"إدراج حرف"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"تطبيق غير معروف"</string>
<string name="sms_control_title" msgid="7296612781128917719">"إرسال رسائل قصيرة SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"تم إرسال عدد كبير من الرسائل القصيرة SMS. المس \"موافق\" للمتابعة، أو \"إلغاء\" لإيقاف الإرسال."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"موافق"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"إلغاء"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"تمت إزالة بطاقة SIM"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"لن تكون شبكة الجوال متاحة حتى تتم إعادة التشغيل وإدخال بطاقة SIM صالحة."</string>
<string name="sim_done_button" msgid="827949989369963775">"تم"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"المس لتعطيل تصحيح أخطاء USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"اختيار أسلوب الإدخال"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"إعداد أسلوب الإدخال"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"لوحة مفاتيح فعلية"</string>
+ <string name="hardware" msgid="7517821086888990278">"أجهزة"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789 أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"العناصر المرشحة"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"تقليل الأيام"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"زيادة الأعوام"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"تقليل الأعوام"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"تم التحديد"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"لم يتم التحديد"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"محدد"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"غير محدد"</string>
- <string name="switch_on" msgid="551417728476977311">"تشغيل"</string>
- <string name="switch_off" msgid="7249798614327155088">"إيقاف"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"مضغوط"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"غير مضغوط"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"إلغاء"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"حذف"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index c78ecd9..c155537 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Дазваляе прыкладанням змяняць паварот экрана ў любы час. Не патрабуецца для звычайных прыкладанняў."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"змена хутк. перамяшч. ўказ."</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Дазваляе прыкладанням змяняць хуткасць курсору мышы або трэкпада ў любы час. Не патрабуецца для звычайных прыкладанняў."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"змяніць раскладку клавіятуры"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Дазваляе прыкладанню змяняць раскладку клавіятуры. Не патрабуецца для звычайных прыкладанняў."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"адправіць сігналы Linux да прыкладанняў"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Дазваляе прыкладанням запытваць адпраўку падаваемага сігнала для ўсiх пастаянных працэсаў."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"прымусіць прыкладанне працаваць заўсёды"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Увядзіце патрэбны PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код"</string>
<string name="select_character" msgid="3365550120617701745">"Уставіць сімвал"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Невядомае прыкладанне"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Адпраўка SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Адпраўляецца вялікая колькасць SMS-паведамленняў. Націсніце \"OK\", каб працягнуць, ці \"Адмена\", каб спыніць адпраўку."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"ОК"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Адмяніць"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-карта выдаленая"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Мабільная сетка будзе недаступная да перазагрузкі з дзеючай SIM-картай."</string>
<string name="sim_done_button" msgid="827949989369963775">"Гатова"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Націсніце, каб адключыць адладку USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Выберыце метад уводу"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Наладзіць метады ўводу"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Фізічная клавіятура"</string>
+ <string name="hardware" msgid="7517821086888990278">"Апар. ср."</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" АБВГДЕЁЖЗІЙКЛМНОПРСТУЎФХЦЧШ\'ЫЬЭЮЯ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"кандыдат."</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Паменшыць лічбу дня"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Павялічыць лічбу года"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Паменшыць лічбу года"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"пастаўлены"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"не пастаўлены"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"абрана"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"не абрана"</string>
- <string name="switch_on" msgid="551417728476977311">"укл."</string>
- <string name="switch_off" msgid="7249798614327155088">"адключаны"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"націснутая"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"не націснутая"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Адмена"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Выдаліць"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 8bcbec3..7860382 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Въведете задължителния ПИН:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"ПИН:"</string>
<string name="select_character" msgid="3365550120617701745">"Вмъкване на знак"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестно приложение"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Изпращане на SMS съобщения"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Изпращат се голям брой SMS съобщения. Докоснете „OK“, за да продължите, или „Отказ“, за да спрете изпращането."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Отказ"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM картата е премахната"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Няма да имате достъп до мобилната мрежа, докато не рестартирате с поставена валидна SIM карта."</string>
<string name="sim_done_button" msgid="827949989369963775">"Готово"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Намаляване на дните"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Увеличаване на годините"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Намаляване на годините"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"отметнато"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"не е отметнато"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"избрано"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"не е избрано"</string>
- <string name="switch_on" msgid="551417728476977311">"включено"</string>
- <string name="switch_off" msgid="7249798614327155088">"изключено"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"натиснато"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"не е натиснато"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Отказ"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Изтриване"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index fdae718..e266a85 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permet que l\'aplicació canviï el gir de la pantalla en qualsevol moment. No s\'hauria de necessitar mai per a les aplicacions normals."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"canvi de velocitat del punter"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permet que l\'aplicació canviï la velocitat del punter del ratolí o del ratolí tàctil en qualsevol moment. No s\'hauria de necessitar mai per a les aplicacions normals."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"canvia la disposició del teclat"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permet que l\'aplicació canviï la disposició del teclat. En principi mai no serà necessari per a les aplicacions normals."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"envia senyals Linux a les aplicacions"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permet que l\'aplicació sol·liciti que el senyal subministrat s\'enviï a tots els processos persistents."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"fes que l\'aplicació s\'executi sempre"</string>
@@ -1009,11 +1007,17 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introdueix el PIN sol·licitat:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Insereix un caràcter"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicació desconeguda"</string>
<string name="sms_control_title" msgid="7296612781128917719">"S\'estan enviant missatges SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"S\'estan enviant molts missatges SMS. Toca D\'acord per continuar o Cancel·la per aturar l\'enviament."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"D\'acord"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancel·la"</string>
+ <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> està enviant molts missatges SMS. Vols permetre que aquesta aplicació continuï enviant missatges?"</string>
+ <string name="sms_control_yes" msgid="3663725993855816807">"Permet"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"Denega"</string>
+ <string name="sms_short_code_confirm_title" msgid="1666863092640877318">"Vols enviar SMS a codi curt?"</string>
+ <string name="sms_premium_short_code_confirm_title" msgid="3811263856304367838">"Vols enviar el SMS prèmium?"</string>
+ <string name="sms_short_code_confirm_message" msgid="5616409294907295407">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> vol enviar un missatge de text a <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, que sembla que és un codi SMS curt.<p>Si envies missatges de text a codis curts, pot ser que es carreguin serveis prèmium al teu compte mòbil.<p>Vols permetre que aquesta aplicació enviï el missatge?"</string>
+ <string name="sms_premium_short_code_confirm_message" msgid="6214083016284738667">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> vol enviar un missatge de text a <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, que és un codi curt SMS prèmium.<p><b>Si envies un missatge a aquesta destinació, es carregaran els serveis prèmium al teu compte mòbil.</b><p>Vols permetre que aquesta aplicació enviï el missatge?"</string>
+ <string name="sms_short_code_confirm_allow" msgid="8957573662645722940">"Envia el missatge"</string>
+ <string name="sms_short_code_confirm_deny" msgid="6374609298084435887">"No enviïs"</string>
+ <string name="sms_short_code_confirm_report" msgid="2588793956061677070">"Informa d\'una aplic. maliciosa"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"Extracció de la targeta SIM"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"La xarxa de telefonia mòbil no estarà disponible fins que no reiniciïs amb una targeta SIM vàlida inserida."</string>
<string name="sim_done_button" msgid="827949989369963775">"Fet"</string>
@@ -1063,10 +1067,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Toca-ho per desactivar la depuració USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Selecció de mètodes d\'introducció"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configura els mètodes d\'entrada"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Teclat físic"</string>
+ <string name="hardware" msgid="7517821086888990278">"Maquinari"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidats"</u></string>
@@ -1193,14 +1195,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Fes disminuir el dia"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Fes augmentar l\'any"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Fes disminuir l\'any"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"marcat"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"no marcat"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"seleccionat"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"no seleccionat"</string>
- <string name="switch_on" msgid="551417728476977311">"activat"</string>
- <string name="switch_off" msgid="7249798614327155088">"desactivat"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"premut"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"no premut"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancel·la"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Suprimeix"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 98b5771..33f91e8 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadejte požadovaný kód PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Vkládání znaků"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznámá aplikace"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Odesílání zpráv SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Odesíláte velký počet zpráv SMS. Dotkněte se možnosti OK, chcete-li pokračovat, nebo možnosti Zrušit, chcete-li odesílání ukončit."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Zrušit"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Karta SIM odebrána"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilní síť bude dostupná až poté, co vložíte platnou kartu SIM a restartujete zařízení."</string>
<string name="sim_done_button" msgid="827949989369963775">"Hotovo"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Ubrat den"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Přidat rok"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Ubrat rok"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"zaškrtnuto"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nezaškrtnuto"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"Vybráno"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"Nevybráno"</string>
- <string name="switch_on" msgid="551417728476977311">"zapnuto"</string>
- <string name="switch_off" msgid="7249798614327155088">"vypnuto"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"stisknuto"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nestisknuto"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Zrušit"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Smazat"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index a3c7474..c5b0e17 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Tillader, at appen kan ændre skærmretningen på et hvilket som helst tidspunkt. Bør aldrig være nødvendigt for almindelige apps."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"ændre markørens hastighed"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Tillader, at appen til enhver tid kan ændre musemarkørens hastighed. Bør aldrig være nødvendigt for normale apps."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"skift tastaturlayout"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Tillader, at appen må ændre tastaturlayoutet. Bør aldrig være nødvendig for normale apps."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"sende Linux-signaler til apps"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Tillader, at appen kan anmode om, at det leverede signal sendes til alle vedholdende processer."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"sørge for, at appen altid kører"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv den påkrævede pinkode:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pinkode:"</string>
<string name="select_character" msgid="3365550120617701745">"Indsæt tegn"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukendt app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sender sms-beskeder"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Der sendes et stort antal sms-beskeder. Vælg \"OK\" for at fortsætte eller \"Annuller\" for at stoppe afsendelsen."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Annuller"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kort blev fjernet"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Det mobile netværk er utilgængeligt, indtil du genstarter med et gyldigt SIM-kort."</string>
<string name="sim_done_button" msgid="827949989369963775">"Udfør"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Tryk for at deaktivere USB-fejlretning."</string>
<string name="select_input_method" msgid="4653387336791222978">"Vælg inputmetode"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Konfigurer inputmetoder"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fysisk tastatur"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Tidligere dag"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Senere år"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Tidligere år"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"markeret"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"ikke markeret"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"udvalgt"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ikke valgt"</string>
- <string name="switch_on" msgid="551417728476977311">"tændt"</string>
- <string name="switch_off" msgid="7249798614327155088">"slukket"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"trykket på"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"ikke trykket på"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annuller"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Slet"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index ae88920..5a24cb3 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Ermöglicht der App, die Bildschirmdrehung jederzeit zu ändern. Sollte nie für normale Apps benötigt werden."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"Zeigergeschwindigkeit ändern"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Ermöglicht der App, jederzeit die Geschwindigkeit des Maus- bzw. Touchpad-Zeigers zu ändern. Sollte nie für normale Apps benötigt werden."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"Tastaturlayout ändern"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Ermöglicht der App, das Tastaturlayout zu ändern. Sollte für normale Apps nie benötigt werden."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Linux-Signale an Apps senden"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Ermöglicht der App, das Senden des gelieferten Signals an alle andauernden Prozesse zu fordern"</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"App permanent ausführen"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Geben Sie die erforderliche PIN ein:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Zeichen einfügen"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Unbekannte App"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Kurznachrichten werden gesendet"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Es wird eine große Anzahl an SMS versendet. Tippen Sie zum Fortfahren auf \"OK\" oder tippen Sie auf \"Abbrechen\", um den Sendevorgang zu beenden."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Abbrechen"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-Karte entfernt"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Das Mobilfunknetz ist erst wieder verfügbar, wenn Sie einen Neustart mit einer gültigen SIM-Karte durchführen."</string>
<string name="sim_done_button" msgid="827949989369963775">"Fertig"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Zum Deaktivieren von USB-Debugging tippen"</string>
<string name="select_input_method" msgid="4653387336791222978">"Eingabemethode wählen"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Eingabemethoden einrichten"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Physische Tastatur"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"Kandidaten"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Tag verringern"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Jahr verlängern"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Jahr verringern"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"Aktiviert"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"Nicht aktiviert"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"Ausgewählt"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"Nicht ausgewählt"</string>
- <string name="switch_on" msgid="551417728476977311">"An"</string>
- <string name="switch_off" msgid="7249798614327155088">"Aus"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"Gedrückt"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"Nicht gedrückt"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Abbrechen"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Löschen"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index ce49a84..7c951cc 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Επιτρέπει στην εφαρμογή την αλλαγή της περιστροφής της οθόνης ανά πάσα στιγμή. Δεν απαιτείται για συνήθεις εφαρμογές."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"αλλαγή ταχύτητας δείκτη"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Επιτρέπει στην εφαρμογή την αλλαγή της ταχύτητας του δείκτη του ποντικιού ή της επιφάνειας αφής ανά πάσα στιγμή. Δεν πρέπει να χρησιμοποιείται από συνήθεις εφαρμογές."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"αλλαγή διάταξης πληκτρολογίου"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Επιτρέπει στην εφαρμογή την αλλαγή της διάταξης πληκτρολογίου. Δεν απαιτείται για συνήθεις εφαρμογές."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"αποστολή σημάτων Linux σε εφαρμογές"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Επιτρέπει στην εφαρμογή την αποστολή αιτήματος για την αποστολή του παρεχόμενου σήματος σε όλες τις υπάρχουσες διαδικασίες."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"να εκτελείται συνεχώς η εφαρμογή"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Πληκτρολογήστε τον απαιτούμενο κωδικό PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Εισαγωγή χαρακτήρα"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Άγνωστη εφαρμογή"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Αποστολή μηνυμάτων SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Αποστέλλεται μεγάλος αριθμός μηνυμάτων SMS. Αγγίξτε το στοιχείο \"OK\" για συνέχεια, ή το στοιχείο \"Ακύρωση\" για διακοπή της αποστολής."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Ακύρωση"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Η κάρτα SIM αφαιρέθηκε"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Το δίκτυο κινητής τηλεφωνίας δεν θα είναι διαθέσιμο μέχρι να κάνετε επανεκκίνηση αφού τοποθετήσετε μια έγκυρη κάρτα SIM."</string>
<string name="sim_done_button" msgid="827949989369963775">"Τέλος"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Αγγίξτε για απενεργοποίηση του εντοπισμού σφαλμάτων USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Επιλογή μεθόδου εισόδου"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Ρύθμιση μεθόδων εισαγωγής"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Φυσικό πληκτρολόγιο"</string>
+ <string name="hardware" msgid="7517821086888990278">"Υλικό"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"υποψήφιοι"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Μείωση ημέρας"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Αύξηση έτους"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Μείωση έτους"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"ελέγχθηκε"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"δεν επιλέχθηκε"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"επιλεγμένο"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"δεν έχει επιλεγεί"</string>
- <string name="switch_on" msgid="551417728476977311">"ενεργοποίηση"</string>
- <string name="switch_off" msgid="7249798614327155088">"απενεργοποιημένη"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"πατήθηκε"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"δεν πατήθηκε"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Ακύρωση"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Διαγραφή"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 38b69fe..208e96a 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Allows the app to change the rotation of the screen at any time. Should never be needed for normal apps."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"change pointer speed"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Allows the app to change the mouse or touch pad pointer speed at any time. Should never be needed for normal apps."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"change keyboard layout"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Allows the app to change the keyboard layout. Should never be needed for normal apps."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"send Linux signals to apps"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Allows the app to request that the supplied signal be sent to all persistent processes."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"make app always run"</string>
@@ -1009,11 +1007,17 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Type the required PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Insert character"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Unknown app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sending SMS messages"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"A large number of SMS messages are being sent. Touch OK to continue or Cancel to stop sending."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancel"</string>
+ <string name="sms_control_message" msgid="3867899169651496433">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> is sending a large number of SMS messages. Do you want to allow this app to continue sending messages?"</string>
+ <string name="sms_control_yes" msgid="3663725993855816807">"Allow"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"Deny"</string>
+ <string name="sms_short_code_confirm_title" msgid="1666863092640877318">"Send SMS to short code?"</string>
+ <string name="sms_premium_short_code_confirm_title" msgid="3811263856304367838">"Send premium SMS?"</string>
+ <string name="sms_short_code_confirm_message" msgid="5616409294907295407">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> would like to send a text message to <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, which appears to be an SMS short code.<p>Sending text messages to some short codes may cause your mobile account to be billed for premium services.<p>do you want to allow this app to send the message?"</string>
+ <string name="sms_premium_short_code_confirm_message" msgid="6214083016284738667">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> would like to send a text message to <b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, which is a premium SMS short code.<p><b>Sending a message to this destination will cause your mobile account to be billed for premium services.</b><p>Do you want to allow this app to send the message?"</string>
+ <string name="sms_short_code_confirm_allow" msgid="8957573662645722940">"Send message"</string>
+ <string name="sms_short_code_confirm_deny" msgid="6374609298084435887">"Don\'t send"</string>
+ <string name="sms_short_code_confirm_report" msgid="2588793956061677070">"Report malicious app"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"SIM card removed"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"The mobile network will be unavailable until you restart with a valid SIM card inserted."</string>
<string name="sim_done_button" msgid="827949989369963775">"Done"</string>
@@ -1063,10 +1067,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Touch to disable USB debugging."</string>
<string name="select_input_method" msgid="4653387336791222978">"Choose input method"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Set up input methods"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Physical keyboard"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidates"</u></string>
@@ -1193,14 +1195,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Decrease day"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Increase year"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Decrease year"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"ticked"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"not ticked"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"selected"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"not selected"</string>
- <string name="switch_on" msgid="551417728476977311">"on"</string>
- <string name="switch_off" msgid="7249798614327155088">"off"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"pressed"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"not pressed"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancel"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index d400734..6f1ee53 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permite que la aplicación cambie la rotación de la pantalla en cualquier momento. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"cambiar velocidad del puntero"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permite que la aplicación cambie la velocidad del puntero del mouse o el trackpad en cualquier momento. Las aplicaciones normales no deben utilizar este permiso."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"Cambiar el diseño del teclado"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permite que la aplicación cambie el diseño del teclado. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"enviar señales de Linux a las aplicaciones"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permite que la aplicación solicite que la señal suministrada se envíe a todos los procesos persistentes."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"hacer que la aplicación se ejecute siempre"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Insertar caracteres"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Se está enviando una gran cantidad de mensajes SMS. Toca \"Aceptar\" para continuar o \"Cancelar\" para detener el envío."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Aceptar"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancelar"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Tarjeta SIM eliminada"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"La red para celulares no estará disponible hasta que reinicies, luego de insertar una tarjeta SIM válida."</string>
<string name="sim_done_button" msgid="827949989369963775">"Finalizado"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Toca para desactivar la depuración de USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Selecciona el método de introducción"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configurar métodos de introducción"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Teclado físico"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Reducir día"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aumentar año"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Reducir año"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"marcado"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"no marcado"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"seleccionado"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"No se ha seleccionado."</string>
- <string name="switch_on" msgid="551417728476977311">"Activado"</string>
- <string name="switch_off" msgid="7249798614327155088">"Desactivado"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"presionado"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"sin presionar"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancelar"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Eliminar"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 6852184..95e1152 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permite que la aplicación cambie la rotación de la pantalla en cualquier momento. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"cambiar velocidad del puntero"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permite que la aplicación modifique la velocidad del puntero del ratón o del trackpad en cualquier momento. Las aplicaciones normales no deberían necesitar este permiso."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"cambiar el diseño del teclado"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permite que la aplicación cambie el diseño del teclado. Las aplicaciones normales no deberían necesitar este permiso."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"enviar señales Linux a aplicaciones"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permite que la aplicación solicite que la señal suministrada se envíe a todos los procesos persistentes."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"hacer que la aplicación se ejecute siempre"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Escribe el PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Insertar carácter"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicación desconocida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensajes SMS..."</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Se va a enviar un número elevado de mensajes SMS. Selecciona Aceptar para continuar o Cancelar para detener el envío."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Aceptar"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancelar"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Tarjeta SIM eliminada"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"La red móvil no estará disponible hasta que reinicies el dispositivo con una tarjeta SIM válida."</string>
<string name="sim_done_button" msgid="827949989369963775">"Listo"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Tocar para inhabilitar la depuración USB"</string>
<string name="select_input_method" msgid="4653387336791222978">"Seleccionar método de introducción"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Ajustar métodos de introducción"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Teclado físico"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Reducir días"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aumentar año"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Reducir año"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"seleccionado"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"no seleccionado"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"seleccionado"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"no seleccionado"</string>
- <string name="switch_on" msgid="551417728476977311">"activado"</string>
- <string name="switch_off" msgid="7249798614327155088">"desactivado"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"pulsado"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"sin pulsar"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancelar"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Eliminar"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 737ab21..a72691f 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Võimaldab rakendusel muuta ekraani pööramist mis tahes ajal. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"kursorikiiruse muutmine"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Võimaldab rakendusel muuta igal ajal hiire- või puutepadjakursori kiirust. Tavarakenduste puhul ei peaks seda kunagi vaja minema."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"klaviatuuri paigutuse muutmine"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Lubab rakendusel muuta klaviatuuri paigutust. Tavarakenduste puhul ei peaks kunagi vaja olema."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Linuxi signaalide saatmine rakendustele"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Võimaldab rakendusel taotleda edastatud signaali saatmist kõigile püsiprotsessidele."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"Rakenduste pidev töös hoidmine"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Sisestage nõutav PIN-kood:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kood:"</string>
<string name="select_character" msgid="3365550120617701745">"Sisesta tähemärk"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Tundmatu rakendus"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-sõnumite saatmine"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Saadetakse suur hulk SMS-sõnumid. Saatmise jätkamiseks valige OK, lõpetamiseks Tühista."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Tühista"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kaart eemaldatud"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobiilsidevõrk ei ole saadaval, kuni sisestate kehtiva SIM-kaardi ja taaskäivitate seadme."</string>
<string name="sim_done_button" msgid="827949989369963775">"Valmis"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Puudutage USB-silumise keelamiseks."</string>
<string name="select_input_method" msgid="4653387336791222978">"Valige sisestusmeetod"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Seadista sisestusmeetodid"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Füüsiline klaviatuur"</string>
+ <string name="hardware" msgid="7517821086888990278">"Riistvara"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSŠZŽTUVWÕÄÖÜXY"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSŠZŽTUVWÕÄÖÜXY"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidaadid"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Päeva vähendamine"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aasta suurendamine"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Aasta vähendamine"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"märgitud"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"pole märgitud"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"valitud"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"pole valitud"</string>
- <string name="switch_on" msgid="551417728476977311">"sees"</string>
- <string name="switch_off" msgid="7249798614327155088">"väljas"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"vajutatud"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"pole vajutatud"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Tühista"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Kustuta"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 80b273b..d33fe74 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"به برنامه اجازه میدهد تا چرخش صفحه را هر وقت بخواهد تغییر دهد. برای برنامههای عادی نیاز نیست."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"تغییر سرعت اشاره گر"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"به برنامه اجازه میدهد تا سرعت ماوس و پد کنترل را هر وقت خواست تغییر دهد. برای برنامههای عادی نیاز نیست."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"تغییر چیدمان صفحه کلید"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"به برنامه اجازه میدهد تا چیدمان صفحه کلید را تغییر دهد. این کار هیچ گاه برای برنامههای عادی نیاز نیست."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"ارسال سیگنالهای Linux به برنامهها"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"به برنامه اجازه میدهد تا درخواست کند سیگنال ارائه شده به همه مراحل دائم ارسال شود."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"همیشه برنامه اجرا شود"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"پین لازم را تایپ کنید:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"پین:"</string>
<string name="select_character" msgid="3365550120617701745">"درج نویسه"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"برنامه ناشناخته"</string>
<string name="sms_control_title" msgid="7296612781128917719">"ارسال پیامک ها"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"تعداد زیادی پیام کوتاه ارسال شده است. برای ادامه، \"تأیید\" را لمس کرده و برای توقف ارسال، \"لغو\" را لمس کنید."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"تأیید"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"لغو"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"سیم کارت برداشته شد"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"تا وقتی که با یک سیمکارت معتبر راهاندازی مجدد نکنید شبکه تلفن همراه غیر قابل دسترس خواهد بود."</string>
<string name="sim_done_button" msgid="827949989369963775">"انجام شد"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"غیرفعال کردن اشکال زدایی USB را لمس کنید."</string>
<string name="select_input_method" msgid="4653387336791222978">"انتخاب روش ورودی"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"تنظیم روشهای ورودی"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"صفحه کلید فیزیکی"</string>
+ <string name="hardware" msgid="7517821086888990278">"سختافزار"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"داوطلبین"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"کاهش روز"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"افزایش سال"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"کاهش سال"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"علامت زده"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"بدون علامت"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"انتخاب شد"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"انتخاب نشده"</string>
- <string name="switch_on" msgid="551417728476977311">"روشن"</string>
- <string name="switch_off" msgid="7249798614327155088">"خاموش"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"فشرده شد"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"فشرده نشد"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"لغو"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index f011ec3..f03e1af 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Antaa sovelluksen muuttaa näytön kiertoa milloin tahansa. Ei tavallisten sovellusten käyttöön."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"muuta osoittimen nopeutta"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Antaa sovelluksen muuttaa hiiren tai kosketuslevyn osoittimen nopeutta milloin tahansa. Ei tavallisten sovellusten käyttöön."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"vaihda näppäimistön asettelua"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Antaa sovelluksen muuttaa näppäimistön asettelua. Ei tavallisten sovellusten käyttöön."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Linux-signaalien lähettäminen sovelluksille"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Antaa sovelluksen pyytää, että tarjottu signaali lähetetään kaikille käynnissä oleville prosesseille."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"sovelluksen asettaminen aina käynnissä olevaksi"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Kirjoita pyydetty PIN-koodi:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-koodi:"</string>
<string name="select_character" msgid="3365550120617701745">"Lisää merkki"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Tuntematon sovellus"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Tekstiviestien lähettäminen"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Olet lähettämässä suurta määrää tekstiviestejä. Jatka koskettamalla OK tai peruuta lähetys valitsemalla Peruuta."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Peruuta"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kortti poistettu"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobiiliverkko ei ole käytettävissä, ennen kuin käynnistät uudelleen kelvollisella laitteeseen kytketyllä SIM-kortilla."</string>
<string name="sim_done_button" msgid="827949989369963775">"Valmis"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Poista USB-vianetsintä käytöstä koskettamalla tätä."</string>
<string name="select_input_method" msgid="4653387336791222978">"Valitse syöttötapa"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Määritä syöttötavat"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fyysinen näppäimistö"</string>
+ <string name="hardware" msgid="7517821086888990278">"Laitteisto"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidaatit"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Vähennä päivien määrää."</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Lisää vuosien määrää."</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Vähennä vuosien määrää."</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"valittu"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"ei valittu"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"valittu"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ei valittu"</string>
- <string name="switch_on" msgid="551417728476977311">"käytössä"</string>
- <string name="switch_off" msgid="7249798614327155088">"pois käytöstä"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"painettu"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"ei painettu"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Peruuta"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Poista"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 7c78654..72ac5f4 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permet à l\'application de changer l\'orientation de l\'écran à tout moment. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"changer la vitesse du pointeur"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permet à l\'application de modifier à tout moment la vitesse du pointeur de la souris ou du pavé tactile. Les applications standards ne doivent jamais avoir recours à cette fonctionnalité."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"changer disposition clavier"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permet à l\'application de changer la disposition du clavier. Les applications standards ne devraient pas nécessiter cette autorisation."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"envoyer des signaux Linux aux applications"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permet à l\'application de demander que le signal fourni soit envoyé à tous les processus persistants."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"exécuter l\'application en continu"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Saisissez le code PIN requis :"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Code PIN :"</string>
<string name="select_character" msgid="3365550120617701745">"Insérer un caractère"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Application inconnue"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Envoi de messages SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Vous êtes sur le point d\'envoyer un grand nombre de SMS. Appuyez sur \"OK\" pour continuer ou sur \"Annuler\" pour interrompre l\'envoi."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Annuler"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Carte SIM retirée"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Le réseau mobile ne sera pas disponible avant le redémarrage avec une carte SIM valide insérée."</string>
<string name="sim_done_button" msgid="827949989369963775">"OK"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Appuyez pour désactiver le débogage USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Sélectionnez le mode de saisie"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configurer les modes de saisie"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Clavier physique"</string>
+ <string name="hardware" msgid="7517821086888990278">"Matériel"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidats"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Jour précédent"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Année suivante"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Année précédente"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"coché"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"non coché"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"sélectionné"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"non sélectionné"</string>
- <string name="switch_on" msgid="551417728476977311">"activé"</string>
- <string name="switch_off" msgid="7249798614327155088">"désactivé"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"sélectionné"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"non sélectionné"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annuler"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Supprimer"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index 1ee3d03..e94e286 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"एप्लिकेशन को किसी भी समय स्क्रीन का रोटेशन बदलने देता है. सामान्य एप्लिकेशन के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"सूचक गति बदलें"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"एप्लिकेशन को माउस या ट्रैकपैड सूचक गति को किसी भी समय बदलने देता है. सामान्य एप्लिकेशन के लिए कभी भी आवश्यक नहीं होना चाहिए."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"कीबोर्ड लेआउट बदलें"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"एप्लिकेशन को कीबोर्ड लेआउट बदलने देता है. सामान्य एप्लिकेशन के लिए कभी आवश्यक नहीं है."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"एप्लिकेशन को Linux सिग्नल भेजें"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"एप्लिकेशन को यह अनुरोध करने देता है कि दिया गया सिग्नल सभी जारी प्रक्रियाओं को भेजा जाए."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"एप्लिकेशन को हमेशा चलने वाला बनाएं"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"आवश्यक पिन लिखें:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"पिन:"</string>
<string name="select_character" msgid="3365550120617701745">"वर्ण सम्मिलित करें"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"अज्ञात एप्लिकेशन"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS संदेश भेज रहा है"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"बड़ी संख्या में SMS संदेश भेजे जा रहे हैं. जारी रखने के लिए ठीक स्पर्श करें, या भेजना बंद करने के लिए रद्द करें स्पर्श करें."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"ठीक"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"रद्द करें"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"सिमकार्ड निकाला गया"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"मान्य सिम कार्ड डालकर पुन: प्रारंभ करने तक मोबाइल नेटवर्क अनुपलब्ध रहेगा."</string>
<string name="sim_done_button" msgid="827949989369963775">"पूर्ण"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"USB डीबग करना अक्षम करने के लिए स्पर्श करें."</string>
<string name="select_input_method" msgid="4653387336791222978">"इनपुट पद्धति चुनें"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"इनपुट पद्धतियां सेट करें"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"भौतिक कीबोर्ड"</string>
+ <string name="hardware" msgid="7517821086888990278">"हार्डवेयर"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"उम्मीदवार"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"दिन कम करें"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"वर्ष बढ़ाएं"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"वर्ष कम करें"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"चेक किया गया"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"चेक नहीं किया गया"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"चयनित"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"चयनित नहीं"</string>
- <string name="switch_on" msgid="551417728476977311">"चालू"</string>
- <string name="switch_off" msgid="7249798614327155088">"बंद"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"दबाया गया"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"दबाया नहीं गया."</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"रद्द करें"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"हटाएं"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 0023310..412bc61 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Upišite potreban PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Umetni znak"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nepoznata aplikacija"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Slanje SMS poruka"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Šalje se velika količina SMS poruka. Dodirnite \"U redu\" za nastavak ili \"Odustani\" za prekid slanja."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"U redu"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Odustani"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM kartica uklonjena"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilna mreža bit će nedostupna do ponovnog pokretanja s umetnutom važećom SIM karticom."</string>
<string name="sim_done_button" msgid="827949989369963775">"Gotovo"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Smanjenje dana"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Povećanje godine"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Smanjenje godine"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"označeno"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nije označeno"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"odabran"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nije odabrano"</string>
- <string name="switch_on" msgid="551417728476977311">"uključeno"</string>
- <string name="switch_off" msgid="7249798614327155088">"isključeno"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"pritisnut"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nije pritisnut"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Odustani"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Izbriši"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 4ae52d5..c8f039f 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Lehetővé teszi az alkalmazás számára a képernyő elforgatásának bármikori módosítását. A normál alkalmazásoknak erre soha nincs szüksége."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"mutató sebességének módosítása"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Lehetővé teszi az alkalmazás számára, hogy bármikor módosítsa az egér vagy az érintőpad mutatójának sebességét. Normál alkalmazásoknak soha nem lehet rá szükségük."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"billentyűzetkiosztás módosítása"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Lehetővé teszi az alkalmazás számára, hogy módosítsa a billentyűzetkiosztást. Normál alkalmazásoknak alapesetben nem lehet szükségük rá."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Linux-jelek küldése az alkalmazásoknak"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Lehetővé teszi az alkalmazás számára, hogy a megadott jelet elküldje az összes állandó folyamatnak."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"az alkalmazás állandó futtatása"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Adja meg a szükséges PIN kódot:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kód:"</string>
<string name="select_character" msgid="3365550120617701745">"Karakter beszúrása"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ismeretlen alkalmazás"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-ek küldése"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Nagy számú SMS-t kíván elküldeni. A folytatáshoz érintse meg az \"OK\", a küldés leállításához a \"Mégse\" lehetőséget."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Mégse"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kártya eltávolítva"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"A mobilhálózat nem lesz elérhető, amíg újra nem indítja egy érvényes SIM kártya behelyezése után."</string>
<string name="sim_done_button" msgid="827949989369963775">"Kész"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Érintse meg az USB hibakeresés kikapcsolásához."</string>
<string name="select_input_method" msgid="4653387336791222978">"Beviteli mód kiválasztása"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Beviteli módok beállítása"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fizikai billentyűzet"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardver"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"jelöltek"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Dátum értékének csökkentése"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Év értékének növelése"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Év értékének csökkentése"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"bejelölve"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nincs bejelölve"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"bejelölve"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nincs kiválasztva"</string>
- <string name="switch_on" msgid="551417728476977311">"be"</string>
- <string name="switch_off" msgid="7249798614327155088">"kikapcsolva"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"megnyomva"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nincs megnyomva"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Mégse"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index bd6294b..03dac8a 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -109,7 +109,7 @@
<string name="cfTemplateRegisteredTime" msgid="6781621964320635172">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string>
<string name="fcComplete" msgid="3118848230966886575">"Kode fitur selesai."</string>
<string name="fcError" msgid="3327560126588500777">"Masalah sambungan atau kode fitur tidak valid."</string>
- <string name="httpErrorOk" msgid="1191919378083472204">"OK"</string>
+ <string name="httpErrorOk" msgid="1191919378083472204">"Oke"</string>
<string name="httpError" msgid="7956392511146698522">"Terjadi kesalahan jaringan."</string>
<string name="httpErrorLookup" msgid="4711687456111963163">"Tidak dapat menemukan URL."</string>
<string name="httpErrorUnsupportedAuthScheme" msgid="6299980280442076799">"Skema autentikasi situs tidak didukung."</string>
@@ -744,7 +744,7 @@
<string name="factorytest_reboot" msgid="6320168203050791643">"Mulai ulang"</string>
<string name="js_dialog_title" msgid="1987483977834603872">"Laman pada \"<xliff:g id="TITLE">%s</xliff:g>\" menyatakan:"</string>
<string name="js_dialog_title_default" msgid="6961903213729667573">"JavaScript"</string>
- <string name="js_dialog_before_unload" msgid="730366588032430474">"Beranjak dari laman ini?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Sentuh OK untuk melanjutkan atau Batal untuk tetap pada laman ini."</string>
+ <string name="js_dialog_before_unload" msgid="730366588032430474">"Beranjak dari laman ini?"\n\n"<xliff:g id="MESSAGE">%s</xliff:g>"\n\n"Sentuh Oke untuk melanjutkan atau Batal untuk tetap pada laman ini."</string>
<string name="save_password_label" msgid="6860261758665825069">"Konfirmasi"</string>
<string name="double_tap_toast" msgid="4595046515400268881">"Kiat: Ketuk dua kali untuk memperbesar dan memperkecil."</string>
<string name="autofill_this_form" msgid="4616758841157816676">"Isiotomatis"</string>
@@ -892,7 +892,7 @@
<string name="VideoView_error_title" msgid="3534509135438353077">"Masalah video"</string>
<string name="VideoView_error_text_invalid_progressive_playback" msgid="3186670335938670444">"Video ini tidak valid untuk pengaliran ke perangkat ini."</string>
<string name="VideoView_error_text_unknown" msgid="3450439155187810085">"Tidak dapat memutar video ini."</string>
- <string name="VideoView_error_button" msgid="2822238215100679592">"OK"</string>
+ <string name="VideoView_error_button" msgid="2822238215100679592">"Oke"</string>
<string name="relative_time" msgid="1818557177829411417">"<xliff:g id="DATE">%1$s</xliff:g>, <xliff:g id="TIME">%2$s</xliff:g>"</string>
<string name="noon" msgid="7245353528818587908">"tengah hari"</string>
<string name="Noon" msgid="3342127745230013127">"Tengah hari"</string>
@@ -916,9 +916,9 @@
<string name="low_internal_storage_view_title" msgid="1399732408701697546">"Ruang penyimpanan tinggal sedikit"</string>
<string name="low_internal_storage_view_text" product="tablet" msgid="4231085657068852042">"Ruang penyimpanan tablet semakin sedikit."</string>
<string name="low_internal_storage_view_text" product="default" msgid="635106544616378836">"Ruang penyimpanan ponsel tersisa sedikit."</string>
- <string name="ok" msgid="5970060430562524910">"OK"</string>
+ <string name="ok" msgid="5970060430562524910">"Oke"</string>
<string name="cancel" msgid="6442560571259935130">"Batal"</string>
- <string name="yes" msgid="5362982303337969312">"OK"</string>
+ <string name="yes" msgid="5362982303337969312">"Oke"</string>
<string name="no" msgid="5141531044935541497">"Batal"</string>
<string name="dialog_alert_title" msgid="2049658708609043103">"Perhatian"</string>
<string name="loading" msgid="7933681260296021180">"Memuat..."</string>
@@ -938,7 +938,7 @@
<string name="anr_activity_process" msgid="5776209883299089767">"Aktivitas <xliff:g id="ACTIVITY">%1$s</xliff:g> tidak menanggapi."\n\n"Anda ingin menutupnya?"</string>
<string name="anr_application_process" msgid="8941757607340481057">"<xliff:g id="APPLICATION">%1$s</xliff:g> tidak menanggapi. Anda ingin menutupnya?"</string>
<string name="anr_process" msgid="6513209874880517125">"Proses <xliff:g id="PROCESS">%1$s</xliff:g> tidak menanggapi."\n\n"Anda ingin menutupnya?"</string>
- <string name="force_close" msgid="8346072094521265605">"OK"</string>
+ <string name="force_close" msgid="8346072094521265605">"Oke"</string>
<string name="report" msgid="4060218260984795706">"Laporkan"</string>
<string name="wait" msgid="7147118217226317732">"Tunggu"</string>
<string name="webpage_unresponsive" msgid="3272758351138122503">"Laman ini tidak menanggapi."\n\n"Apakah Anda ingin menutupnya?"</string>
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ketik PIN yang diminta:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Sisipkan huruf"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tak dikenal"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Mengirim pesan SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Pesan SMS dalam jumlah besar sedang dikirimkan. Sentuh OK untuk melanjutkan, atau Batal untuk menghentikan pengiriman."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Batal"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Kartu SIM dihapus"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Jaringan seluler tidak akan tersedia sampai Anda memulai lagi dengan memasukkan kartu SIM yang valid."</string>
<string name="sim_done_button" msgid="827949989369963775">"Selesai"</string>
@@ -1048,7 +1064,7 @@
<string name="dlg_confirm_kill_storage_users_title" msgid="963039033470478697">"Hidupkan penyimpanan USB"</string>
<string name="dlg_confirm_kill_storage_users_text" msgid="5100428757107469454">"Jika Anda menyalakan penyimpanan USB, beberapa apl yang Anda gunakan akan berhenti dan mungkin tidak dapat dibuka hingga penyimpanan USB dimatikan."</string>
<string name="dlg_error_title" msgid="7323658469626514207">"Operasi USB gagal"</string>
- <string name="dlg_ok" msgid="7376953167039865701">"OK"</string>
+ <string name="dlg_ok" msgid="7376953167039865701">"Oke"</string>
<string name="usb_mtp_notification_title" msgid="3699913097391550394">"Tersambung sebagai perangkat media"</string>
<string name="usb_ptp_notification_title" msgid="1960817192216064833">"Tersambung sebagai kamera"</string>
<string name="usb_cd_installer_notification_title" msgid="6774712827892090754">"Tersambung sebagai pemasang"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Kurangi hari"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Tambah tahun"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Kurangi tahun"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"dicentang"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"tidak diperiksa"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"dipilih"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"tidak dipilih"</string>
- <string name="switch_on" msgid="551417728476977311">"nyala"</string>
- <string name="switch_off" msgid="7249798614327155088">"mati"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"ditekan"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"tidak ditekan"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Batal"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Hapus"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index ae52c62..e9e5924 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Consente all\'applicazione di cambiare la rotazione dello schermo in qualsiasi momento. Non dovrebbe mai essere necessario per le normali applicazioni."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"cambio velocità del puntatore"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Consente all\'applicazione di modificare la velocità del puntatore del mouse o del trackpad in qualsiasi momento. Non dovrebbe mai essere necessaria per le applicazioni normali."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"modifica del layout tastiera"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Consente all\'applicazione di modificare il layout della tastiera. Non dovrebbe mai essere necessaria per le normali applicazioni."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"invio segnali Linux alle applicazioni"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Consente all\'applicazione di richiedere l\'invio del segnale fornito a tutti i processi persistenti."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"esecuzione permanente delle applicazioni"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Inserisci il PIN richiesto:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Inserisci carattere"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Applicazione sconosciuta"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Invio SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"È in corso l\'invio di numerosi SMS. Tocca \"OK\" per continuare oppure \"Annulla\" per interrompere l\'invio."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Annulla"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Scheda SIM rimossa"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"La rete mobile non sarà disponibile finché non eseguirai il riavvio con una scheda SIM valida inserita."</string>
<string name="sim_done_button" msgid="827949989369963775">"Fine"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Tocca per disattivare il debug USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Scegli il metodo di immissione"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configura metodi di immissione"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Tastiera fisica"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidati"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Riduci giorno"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aumenta anno"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Riduci anno"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"selezionata"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"non selezionato"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"selezionato"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"non selezionato"</string>
- <string name="switch_on" msgid="551417728476977311">"attivo"</string>
- <string name="switch_off" msgid="7249798614327155088">"disattivo"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"premuto"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"non premuto"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annulla"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Canc"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 613410f..731546c 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"מאפשר ליישום לשנות את הסיבוב של המסך בכל עת. הרשאה זו לעולם אינה נחוצה ליישומים רגילים."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"שינוי מהירות המצביע"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"מאפשר ליישום לשנות את המהירות של מצביע העכבר או לוח המגע בכל עת. יישומים רגילים לא אמורים לעולם להזדקק להרשאה זו."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"שנה את פריסת המקלדת"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"מאפשר ליישום לשנות את פריסת המקלדת. הרשאה זו לעולם אינה אמורה להיות נחוצה עבור יישומים רגילים."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"שליחת אותות Linux ליישומים"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"מאפשר ליישום לבקש שהאות שנקלט יישלח לכל התהליכים המתמשכים."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"הגדרת היישום לפעול תמיד"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"הקלד את קוד ה-PIN הנדרש."</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"הוסף תו"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"יישום לא ידוע"</string>
<string name="sms_control_title" msgid="7296612781128917719">"שולח הודעות SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"מתבצעת כעת שליחה של מספר גדול של הודעות SMS. גע באפשרות \'אישור\' כדי להמשיך, או \'ביטול\' כדי לעצור את השליחה."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"אישור"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"ביטול"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"כרטיס ה-SIM הוסר"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"הרשת הסלולרית לא תהיה זמינה עד שתפעיל מחדש לאחר הכנסת כרטיס SIM חוקי."</string>
<string name="sim_done_button" msgid="827949989369963775">"סיום"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"גע כדי להשבית את ניקוי הבאגים בהתקן ה-USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"בחר שיטת הזנה"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"הגדר שיטות קלט"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"מקלדת פיזית"</string>
+ <string name="hardware" msgid="7517821086888990278">"חומרה"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"מועמדים"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"הפחת יום"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"הוסף שנה"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"הפחת שנה"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"מסומן"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"לא מסומן"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"נבחר"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"לא נבחר"</string>
- <string name="switch_on" msgid="551417728476977311">"מופעל"</string>
- <string name="switch_off" msgid="7249798614327155088">"כבוי"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"לחוץ"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"לא לחוץ"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"ביטול"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"מחק"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 5e5010d..e9d3558 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"いつでも画面の向きを変更することをアプリに許可します。通常のアプリでは不要です。"</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"ポインタの速度の変更"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"マウスまたはトラックパッドのポインタの速度をいつでも変更することをアプリに許可します。通常のアプリでは不要です。"</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"キーボードレイアウトの変更"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"キーボードレイアウトの変更をアプリに許可します。通常のアプリでは不要です。"</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"アプリへのLinuxシグナルの送信"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"受信したシグナルをすべての継続プロセスに送信するようリクエストすることをアプリに許可します。"</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"アプリの常時実行"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"必要なPINを入力してください:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"文字を挿入"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"不明なアプリ"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMSメッセージの送信中"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"大量のSMSメッセージを送信しようとしています。[OK]で送信、[キャンセル]で中止します。"</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"キャンセル"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIMカードが取り外されました"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"有効なSIMカードを挿入して再起動するまでは、モバイルネットワークは利用できません。"</string>
<string name="sim_done_button" msgid="827949989369963775">"完了"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"タップしてUSBデバッグを無効にします。"</string>
<string name="select_input_method" msgid="4653387336791222978">"入力方法の選択"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"入力方法をセットアップ"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"物理キーボード"</string>
+ <string name="hardware" msgid="7517821086888990278">"ハードウェア"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"候補"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"1日戻します"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"1年進めます"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"1年戻します"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"ON"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"OFF"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"ON"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"選択されていません"</string>
- <string name="switch_on" msgid="551417728476977311">"ON"</string>
- <string name="switch_off" msgid="7249798614327155088">"OFF"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"ON"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"OFF"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"キャンセル"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"削除"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 5556aae..02ab306 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"앱이 언제든지 화면 회전을 변경할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"포인터 속도 변경"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"앱이 언제든지 마우스 또는 트랙패드 포인터의 속도를 변경할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"키보드 레이아웃 변경"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"앱이 키보드 레이아웃을 변경할 수 있도록 허용합니다. 일반 앱에는 필요하지 않습니다."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"앱에 Linux 시그널 보내기"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"제공된 시그널을 모든 영구 프로세스로 전송하는 것을 앱이 요청할 수 있도록 허용합니다."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"앱이 항상 실행되도록 설정"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"필수 PIN 입력:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"문자 삽입"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"알 수 없는 앱"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS 메시지를 보내는 중"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"SMS 메시지를 대량 전송 중입니다. 계속하려면 \'확인\'을 터치하고 전송을 중지하려면 \'취소\'를 터치하세요."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"확인"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"취소"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM 카드 제거됨"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"유효한 SIM 카드를 삽입하여 다시 시작할 때까지 모바일 네트워크를 사용할 수 없습니다."</string>
<string name="sim_done_button" msgid="827949989369963775">"완료"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"USB 디버깅을 사용하지 않으려면 터치하세요."</string>
<string name="select_input_method" msgid="4653387336791222978">"입력 방법 선택"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"입력 방법 설정"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"물리적 키보드"</string>
+ <string name="hardware" msgid="7517821086888990278">"하드웨어"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"가능한 원인"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"\'일\'을 줄입니다."</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"\'연도\'를 늘립니다."</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"\'연도\'를 줄입니다."</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"확인"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"선택 안함"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"선택됨"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"선택 안함"</string>
- <string name="switch_on" msgid="551417728476977311">"켜짐"</string>
- <string name="switch_off" msgid="7249798614327155088">"꺼짐"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"누름"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"누르지 않음"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt 키"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"취소"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete 키"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index 9e82e8c..3d389d8 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Leidžiama programai bet kada kaitalioti ekraną. Įprastoms programoms to neturėtų prireikti."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"keisti žymiklio greitį"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Leidžiama programai keisti pelės ar sensorinio pulto žymiklio greitį. Įprastoms programoms to neturėtų prireikti."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"keisti klaviatūros išdėstymą"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Leidžiama programai pakeisti klaviatūros išdėstymą. Įprastoms programoms to neturėtų prireikti."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"siųsti „Linux“ signalus programoms"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Leidžiama programai pateikti užklausą, kad teikiamas signalas būtų siunčiamas visiems nuolatiniams procesams."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"nustatyti, kad programa būtų visada vykdoma"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Įveskite reikiamą PIN kodą:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN kodas:"</string>
<string name="select_character" msgid="3365550120617701745">"Įterpti simbolį"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nežinoma programa"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS pranešimų siuntimas"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Siunčiama daug SMS pranešimų. Palieskite „Gerai“, jei norite tęsti, arba „Atšaukti“, jei norite sustabdyti siuntimą."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Gerai"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Atšaukti"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM kortelė pašalinta"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilusis tinklas bus nepasiekiamas, kol nepaleisite iš naujo įdėję tinkamą SIM kortelę."</string>
<string name="sim_done_button" msgid="827949989369963775">"Atlikta"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Palieskite, kad neleistumėte USB derinimo."</string>
<string name="select_input_method" msgid="4653387336791222978">"Pasirinkite įvesties metodą"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Nustatyti įvesties metodus"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fizinė klaviatūra"</string>
+ <string name="hardware" msgid="7517821086888990278">"Apar. įr."</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidatai"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Sumažinti dienų skaičių"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Padidinti metų skaičių"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Sumažinti metų skaičių"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"pažymėtas"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nepatikrinta"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"pasirinkta"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nepasirinkta"</string>
- <string name="switch_on" msgid="551417728476977311">"įjungta"</string>
- <string name="switch_off" msgid="7249798614327155088">"išjungta"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"paspausta"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nepaspausta"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Atšaukti"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Ištrinti"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index ae451e0..05d0dbe 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Ļauj lietotnei jebkurā laikā mainīt ekrāna pozīciju. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"Rādītāja ātruma mainīšana"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Ļauj lietotnei jebkurā laikā mainīt peles vai skārienpaliktņa rādītāja ātrumu. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"Tastatūras izkārtojuma maiņa"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Ļauj lietotnei mainīt tastatūras izkārtojumu. Parastajām lietotnēm tas nekad nav nepieciešams."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"sūtīt Linux signālus lietotnēm"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Ļauj lietotnei pieprasīt, lai piegādātais signāls tiktu sūtīts visiem pastāvīgajiem procesiem."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"likt lietotnei vienmēr darboties"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ierakstiet pieprasīto PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Ievietojiet rakstzīmi"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nezināma lietotne"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Īsziņu sūtīšana"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Tiek sūtīts daudz īsziņu. Pieskarieties Labi, lai turpinātu, vai Atcelt, lai apturētu sūtīšanu."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Labi"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Atcelt"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM karte ir izņemta."</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilais tīkls nebūs pieejams līdz brīdim, kad restartēsiet ierīci ar ievietotu derīgu SIM karti."</string>
<string name="sim_done_button" msgid="827949989369963775">"Gatavs"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Pieskarieties, lai atspējotu USB atkļūdošanu."</string>
<string name="select_input_method" msgid="4653387336791222978">"Ievades metodes izvēle"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Iestatīt ievades metodes"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fiziskā tastatūra"</string>
+ <string name="hardware" msgid="7517821086888990278">"Aparatūra"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789AĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidāti"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Norādīt agrāku dienu"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Norādīt vēlāku gadu"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Norādīt agrāku gadu"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"atzīmēta"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nav atzīmēta"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"atlasīta"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nav atlasīta"</string>
- <string name="switch_on" msgid="551417728476977311">"ieslēgts"</string>
- <string name="switch_off" msgid="7249798614327155088">"izslēgts"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"nospiesta"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nav nospiesta"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alternēšanas taustiņš"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Atcelt"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Dzēšanas taustiņš"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index ead175b..f0f5630 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Membenarkan apl untuk menukar putaran skrin pada bila-bila masa. Tidak sekali-kali diperlukan untuk apl biasa."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"tukar kelajuan penuding"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Membenarkan apl untuk menukar kelajuan penunjuk tetikus atau pad jejak pada bila-bila masa. Tidak sekali-kali diperlukan untuk apl biasa."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"tukar susun atur papan kekunci"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Membenarkan apl menukar susun atur papan kekunci. Tidak sekali-kali diperlukan untuk apl biasa."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"hantar isyarat Linux kepada apl"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Membenarkan apl meminta isyarat yang dibekalkan dihantar kepada semua proses yang berterusan."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"buatkan apl sentiasa berjalan"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Taipkan PIN yang diperlukan:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Masukkan aksara"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Apl tidak diketahui"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Menghantar mesej SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Sejumlah besar mesej SMS sedang dihantar. Sentuh \"OK\" untuk meneruskan atau \"Batal\" untuk menghentikan penghantaran."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Batal"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Kad SIM dikeluarkan"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Rangkaian mudah alih tidak akan tersedia sehingga anda mula semula dengan kad SIM yang sah dimasukkan."</string>
<string name="sim_done_button" msgid="827949989369963775">"Selesai"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Sentuh untuk melumpuhkan penyahpepijatan USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Pilih kaedah input"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Sediakan kaedah input"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Papan kekunci fizikal"</string>
+ <string name="hardware" msgid="7517821086888990278">"Perkakasan"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"calon"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Kurangkan hari"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Tingkatkan tahun"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Kurangkan tahun"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"ditandakan"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"tidak ditandakan"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"dipilih"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"tidak dipilih"</string>
- <string name="switch_on" msgid="551417728476977311">"hidup"</string>
- <string name="switch_off" msgid="7249798614327155088">"mati"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"ditekan."</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"tidak ditekan"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Batal"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Padam"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index bbd846f..6609d29 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Skriv inn påkrevd PIN-kode:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Sett inn tegn"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ukjent app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Sender SMS-meldinger"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Et stort antall SMS-meldinger sendes. Trykk på OK for å fortsette, eller trykk på Avbryt for å avbryte sendingen."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Avbryt"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kort er fjernet"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Det mobile nettverket forblir utilgjengelig inntil du starter på nytt med et gyldig SIM-kort."</string>
<string name="sim_done_button" msgid="827949989369963775">"Fullført"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Reduser dager"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Øk år"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Reduser år"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"valgt"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"ikke valgt"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"valgt"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ikke valgt"</string>
- <string name="switch_on" msgid="551417728476977311">"på"</string>
- <string name="switch_off" msgid="7249798614327155088">"av"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"trykket"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"ikke trykket"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Avbryt"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Slett"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 8440d62..1c8809a 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Hiermee kan de app de rotatie van het scherm op elk moment wijzigen. Nooit vereist voor normale apps."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"aanwijzersnelheid wijzigen"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Hiermee kan de app de snelheid van de muis- of trackpadaanwijzer op elk moment wijzigen. Nooit vereist voor normale apps."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"toetsenbordindeling wijzigen"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Hiermee kan de app de toetsenbordindeling wijzigen. Nooit vereist voor normale apps."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Linux-signalen verzenden naar apps"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Hiermee kan de app ervoor zorgen dat het geleverde signaal wordt verzonden naar alle persistente processen."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"app altijd laten uitvoeren"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Voer de gewenste pincode in:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Pincode"</string>
<string name="select_character" msgid="3365550120617701745">"Teken invoegen"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Onbekende app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS-berichten verzenden"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Er wordt een groot aantal sms\'jes verzonden. Raak \'OK\' aan om door te gaan of \'Annuleren\' om de verzending te stoppen."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Annuleren"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Simkaart verwijderd"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Het mobiele netwerk is niet beschikbaar totdat u het apparaat opnieuw start met een geldige simkaart."</string>
<string name="sim_done_button" msgid="827949989369963775">"Gereed"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Raak deze optie aan om USB-foutopsporing uit te schakelen."</string>
<string name="select_input_method" msgid="4653387336791222978">"Invoermethode selecteren"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Invoermethoden instellen"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fysiek toetsenbord"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidaten"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Lagere waarde voor dag"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Hogere waarde voor jaar"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Lagere waarde voor jaar"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"aangevinkt"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"niet aangevinkt"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"geselecteerd"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"niet geselecteerd"</string>
- <string name="switch_on" msgid="551417728476977311">"aan"</string>
- <string name="switch_off" msgid="7249798614327155088">"uit"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"ingedrukt"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"niet ingedrukt"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Annuleren"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index ee1e6a3..83b7aba 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Pozwala aplikacji na zmianę obrotu ekranu w dowolnym momencie. To uprawnienie nie powinno być potrzebne zwykłym aplikacjom."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"zmiana szybkości wskaźnika"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Pozwala aplikacji zmienić szybkość wskaźnika myszy lub touchpada w dowolnym momencie. Nieprzeznaczone dla zwykłych aplikacji."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"zmiana układu klawiatury"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Zezwala aplikacji na zmianę układu klawiatury. Nieprzeznaczone dla zwykłych aplikacji."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"wysyłanie sygnałów systemu Linux do aplikacji"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Pozwala aplikacji na żądanie, aby dostarczony sygnał został wysłany do wszystkich trwałych procesów."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"sprawianie, że aplikacja jest cały czas uruchomiona"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Wpisz wymagany kod PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Kod PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Wstaw znak"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Nieznana aplikacja"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Wysyłanie wiadomości SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Wysyłana jest duża liczba wiadomości SMS. Dotknij OK, aby kontynuować, lub Anuluj, aby zatrzymać wysyłanie."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Anuluj"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Karta SIM wyjęta"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Sieć komórkowa będzie niedostępna do chwili ponownego uruchomienia urządzenia z użyciem ważnej karty SIM."</string>
<string name="sim_done_button" msgid="827949989369963775">"Gotowe"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Dotknij, aby wyłączyć debugowanie USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Wybierz metodę wprowadzania"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Konfiguruj metody wprowadzania"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Klawiatura fizyczna"</string>
+ <string name="hardware" msgid="7517821086888990278">"Sprzęt"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" AĄBCĆDEĘFGHIJKLŁMNŃOÓPQRSŚTUVWXYZŹŻ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandydaci"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Zmień dzień na wcześniejszy"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Zmień rok na późniejszy"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Zmień rok na wcześniejszy"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"zaznaczono"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nie zaznaczono"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"wybrano"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nie wybrano"</string>
- <string name="switch_on" msgid="551417728476977311">"włączono"</string>
- <string name="switch_off" msgid="7249798614327155088">"wyłączono"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"naciśnięto"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nie naciśnięto"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Anuluj"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 393ba17..8f2f78f 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permite que a aplicação altere a rotação do ecrã em qualquer momento. Nunca deve ser necessário para aplicações normais."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"alterar a veloc. do ponteiro"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permite à aplicação mudar em qualquer altura a velocidade do ponteiro do rato ou do trackpad. Nunca deverá ser necessário para aplicações normais."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"alterar o esquema de teclado"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permite que a aplicação altere o esquema de teclado. Nunca deverá ser necessário para aplicações normais."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"enviar sinais Linux para aplicações"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permite à aplicação pedir que o sinal fornecido seja enviado a todos os processos persistentes."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"fazer com que a aplicação seja sempre executada"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduza o PIN solicitado:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Introduzir carácter"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicação desconhecida"</string>
<string name="sms_control_title" msgid="7296612781128917719">"A enviar mensagens SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Está a ser enviado um grande número de mensagens SMS. Toque em “OK” para continuar ou “Cancelar” para parar o envio."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancelar"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Cartão SIM removido"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"A rede de telemóvel estará indisponível até que reinicie o aparelho com um cartão SIM válido inserido."</string>
<string name="sim_done_button" msgid="827949989369963775">"Concluído"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Toque para desativar a depuração USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Escolher o método de entrada"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configurar métodos de introdução"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Teclado físico"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Diminuir dia"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aumentar ano"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Diminuir ano"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"marcado"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"desmarcado"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"selecionado"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"não selecionado"</string>
- <string name="switch_on" msgid="551417728476977311">"ativado"</string>
- <string name="switch_off" msgid="7249798614327155088">"desativado"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"premido"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"não premido"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancelar"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index bb145f7..4d770b5 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Permite que o aplicativo gire a tela a qualquer momento. Nunca deve ser necessário para aplicativos normais."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"alterar velocidade do ponteiro"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Permite que o aplicativo altere a velocidade do cursos do mouse ou trackpad a qualquer momento. Nunca deve ser necessário para aplicativos normais."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"alterar layout do teclado"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Permite que o aplicativo altere o layout do teclado. Não será necessário para aplicativos normais."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"enviar sinais para aplicativos Linux"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Permite que o aplicativo solicite o envio do sinal fornecido a todos os processos persistentes."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"sempre executar o aplicativo"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Digite o PIN obrigatório:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Inserir caractere"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicativo desconhecido"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Enviando mensagens SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Muitas mensagens SMS estão sendo enviadas. Selecione \"OK\" para continuar ou \"Cancelar\" para interromper o envio."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Cancelar"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Cartão SIM removido"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"A rede móvel ficará indisponível até que você reinicie com um cartão SIM válido inserido."</string>
<string name="sim_done_button" msgid="827949989369963775">"Concluído"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Toque para desativar a depuração do USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Selecione o método de entrada"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Configurar métodos de entrada"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Teclado físico"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"candidatos"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Diminuir dia"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Aumentar ano"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Diminuir ano"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"verificado"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"não selecionado"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"selecionado"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"Não selecionado"</string>
- <string name="switch_on" msgid="551417728476977311">"ativado"</string>
- <string name="switch_off" msgid="7249798614327155088">"desativado"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"pressionado"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"não pressionado"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Cancelar"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Excluir"</string>
diff --git a/core/res/res/values-rm/strings.xml b/core/res/res/values-rm/strings.xml
index 8b0718f..6f066de 100644
--- a/core/res/res/values-rm/strings.xml
+++ b/core/res/res/values-rm/strings.xml
@@ -1489,13 +1489,27 @@
<!-- no translation found for wifi_p2p_show_pin_message (8530563323880921094) -->
<skip />
<string name="select_character" msgid="3365550120617701745">"Inserir in caracter"</string>
- <!-- no translation found for sms_control_default_app_name (3058577482636640465) -->
- <skip />
<string name="sms_control_title" msgid="7296612781128917719">"Trametter messadis SMS"</string>
- <!-- no translation found for sms_control_message (4073755190243093924) -->
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
<skip />
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Interrumper"</string>
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<!-- no translation found for sim_removed_title (6227712319223226185) -->
<skip />
<!-- no translation found for sim_removed_message (2333164559970958645) -->
@@ -1788,22 +1802,6 @@
<skip />
<!-- no translation found for date_picker_decrement_year_button (4482021813491121717) -->
<skip />
- <!-- no translation found for checkbox_checked (7222044992652711167) -->
- <skip />
- <!-- no translation found for checkbox_not_checked (5174639551134444056) -->
- <skip />
- <!-- no translation found for radiobutton_selected (8603599808486581511) -->
- <skip />
- <!-- no translation found for radiobutton_not_selected (2908760184307722393) -->
- <skip />
- <!-- no translation found for switch_on (551417728476977311) -->
- <skip />
- <!-- no translation found for switch_off (7249798614327155088) -->
- <skip />
- <!-- no translation found for togglebutton_pressed (4180411746647422233) -->
- <skip />
- <!-- no translation found for togglebutton_not_pressed (4495147725636134425) -->
- <skip />
<!-- no translation found for keyboardview_keycode_alt (4856868820040051939) -->
<skip />
<!-- no translation found for keyboardview_keycode_cancel (1203984017245783244) -->
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index d93d8d1..3772861 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Introduceţi codul PIN necesar:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Cod PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Introduceţi caracterul"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Aplicaţie necunoscută"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Se trimit mesaje SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"În acest moment se trimit multe mesaje SMS. Atingeţi „OK” pentru a continua sau „Anulaţi” pentru a opri trimiterea."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Anulaţi"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Card SIM eliminat"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Reţeaua mobilă va fi indisponibilă până când reporniţi cu o cartelă SIM validă introdusă."</string>
<string name="sim_done_button" msgid="827949989369963775">"Terminat"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Reduceţi valoarea pentru zi"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Creşteţi valoarea pentru an"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Reduceţi valoarea pentru an"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"bifată"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nebifată"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"selectat"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"neselectat"</string>
- <string name="switch_on" msgid="551417728476977311">"activat"</string>
- <string name="switch_off" msgid="7249798614327155088">"dezactivat"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"apăsat"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"neapăsat"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Anulaţi"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Ştergeţi"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 2743763..b967620 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Приложение сможет менять ориентацию экрана. Это разрешение не используется обычными приложениями."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"изменять скорость указателя"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Приложение сможет в любой момент изменить скорость движения указателя мыши или сенсорной панели. Это разрешение не используется обычными приложениями."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"менять раскладку автоматически"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Разрешить автоматическую смену раскладки клавиатуры (используется лишь в некоторых приложениях)."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"отправка сигналов Linux приложениям"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Приложение сможет запрашивать передачу полученного сигнала всем постоянным процессам."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"постоянная работа приложения"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введите PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string>
<string name="select_character" msgid="3365550120617701745">"Введите символ"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Неизвестное приложение"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Отправка SMS-сообщений"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Отправляется большое количество SMS-сообщений. Нажмите \"ОК\", чтобы продолжить отправку, или \"Отмена\", чтобы прекратить ее."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"ОК"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Отмена"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-карта удалена"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Пока вы не вставите действующую SIM-карту, мобильная сеть будет недоступна."</string>
<string name="sim_done_button" msgid="827949989369963775">"Готово"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Нажмите, чтобы отключить отладку по USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Выберите способ ввода"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Настройка способов ввода"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Физическая клавиатура"</string>
+ <string name="hardware" msgid="7517821086888990278">"Аппаратура"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"варианты"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"На день назад"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"На год вперед"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"На год назад"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"установлено"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"не установлено"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"выбрано"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"не выбрано"</string>
- <string name="switch_on" msgid="551417728476977311">"Включено"</string>
- <string name="switch_off" msgid="7249798614327155088">"Выкл."</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"нажато"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"не нажато"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Клавиша ALT"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Отмена"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Клавиша удаления"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 49d04c3..1bf7a17 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Umožňuje aplikácii kedykoľvek zmeniť otáčanie obrazovky. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"zmena rýchlosti ukazovateľa"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Umožňuje aplikácii kedykoľvek zmeniť rýchlosť kurzora myši alebo touchpadu. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"zmeniť rozloženie klávesnice"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Umožňuje aplikácii zmeniť rozloženie klávesnice. Bežné aplikácie by toto nastavenie nemali nikdy potrebovať."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"odoslať aplikáciám signály systému Linux"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Umožňuje aplikácii vyžiadať odoslanie poskytnutého signálu všetkým trvalým procesom."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"nastaviť, aby bola aplikácia neustále spustená"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Zadajte požadovaný kód PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Vkladanie znakov"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznáma aplikácia"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Odosielanie správ SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Odosiela sa veľký počet správ SMS. Ak chcete pokračovať, dotknite sa tlačidla OK. Ak chcete odosielanie ukončiť, dotknite sa tlačidla Zrušiť."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Zrušiť"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Karta SIM bola odobraná"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilná sieť nebude k dispozícii, kým nevložíte platnú kartu SIM a zariadenie nereštartujete."</string>
<string name="sim_done_button" msgid="827949989369963775">"Hotovo"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Dotknutím zakážete ladenie USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Zvoliť metódu vstupu"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Nastavenie metód vstupu"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fyzická klávesnica"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardvér"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" AÁÄBCČDĎDZDŽEÉFGHCHIÍJKLĽMNŇOÓÔPRŔSŠTŤUÚVWXYÝZŽ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidáti"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Ubrať deň"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Pridať rok"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Ubrať rok"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"začiarknuté"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"nezačiarknuté"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"vybratý"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"nie je vybraté"</string>
- <string name="switch_on" msgid="551417728476977311">"zapnuté"</string>
- <string name="switch_off" msgid="7249798614327155088">"vypnuté"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"stlačené"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"nestlačené"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Zrušiť"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Odstrániť"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 55f3593..152d1c1 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Programu omogoča, da kadar koli zasuka zaslon. Ne uporabljajte za navadne programe."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"spreminjanje hitrosti kazalca"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Programu omogoča spreminjanje hitrosti kazalca miške ali sledilne ploščice. Tega ni treba nikoli uporabiti za navadne programe."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"spreminjanje postavitve tipkovnice"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Aplikaciji omogoča, da kadar koli spremeni postavitev tipkovnice. Običajne aplikacije tega ne potrebujejo."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"pošiljanje signalov Linuxa programom"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Programu omogoča, da zahteva, da je posredovani signal poslan vsem trajnim procesom."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"neprekinjeno izvajanje programov"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Vnesite zahtevano kodo PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Vstavljanje znaka"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Neznan program"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Pošiljanje sporočil SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"V pošiljanju je veliko sporočil SMS. Če želite nadaljevati, izberite »V redu«. Če želite pošiljanje ustaviti, izberite »Prekliči«."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"V redu"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Prekliči"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Kartica SIM odstranjena"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mobilno omrežje ne bo na voljo, dokler naprave vnovič ne zaženete z veljavno kartico SIM."</string>
<string name="sim_done_button" msgid="827949989369963775">"Dokončano"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Dotaknite se, če želite onemogočiti iskanje in odpravljanje napak prek vrat USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Izberite način vnosa"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Nastavi načine vnosa"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fizična tipkovnica"</string>
+ <string name="hardware" msgid="7517821086888990278">"Strojna oprema"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidati"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Zmanjšanje vrednosti za dan"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Povečanje vrednosti za leto"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Zmanjšanje vrednosti za leto"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"potrjeno"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"ni odkljukano"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"izbrano"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ni izbrano"</string>
- <string name="switch_on" msgid="551417728476977311">"vklopljeno"</string>
- <string name="switch_off" msgid="7249798614327155088">"izklopljeno"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"vklopljen"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"izklopljen"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Tipka Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Prekliči"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Tipka Delete"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index de03bec..9e63b31 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Унесите потребни PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Уметање знака"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Непозната апликација"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Слање SMS порука"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Шаље се велики број SMS порука. Додирните Потврди да бисте наставили или Откажи да бисте зауставили слање."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Потврди"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Откажи"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM картица је уклоњена"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Мобилна мрежа неће бити доступна док не покренете систем поново уз уметање важеће SIM картице."</string>
<string name="sim_done_button" msgid="827949989369963775">"Готово"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Смањивање дана"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Повећавање године"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Смањивање године"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"изабрано"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"није потврђено"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"изабрано"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"није изабрано"</string>
- <string name="switch_on" msgid="551417728476977311">"укључено"</string>
- <string name="switch_off" msgid="7249798614327155088">"искључено"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"притиснуто"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"није притиснуто"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Откажи"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Избриши"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 7448ff8..3db9ec7 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Gör att appen när som helst kan ändra skärmläget. Behövs inte för vanliga appar."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"ändra markörens hastighet"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Tillåter att appen när som helst ändrar hastigheten för musens eller styrplattans markör. Ska inte behövas för vanliga appar."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"ändra tangentbordslayout"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Tillåter att appen ändrar tangentbordets layout. Ska inte behövas för vanliga appar."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"skicka Linux-signaler till appar"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Tillåter att appen begär att den angivna signalen skickas till alla beständiga processer."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"se till att appen alltid körs"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Ange den obligatoriska PIN-koden:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-kod:"</string>
<string name="select_character" msgid="3365550120617701745">"Infoga tecken"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Okänd app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Skickar SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Flera SMS skickas. Tryck på OK om du vill fortsätta eller på Avbryt om du vill avsluta sändningen."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Avbryt"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-kortet togs bort"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Det mobila nätverket kommer inte att vara tillgängligt förrän du startar om med ett giltigt SIM-kort."</string>
<string name="sim_done_button" msgid="827949989369963775">"Klar"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Tryck om du vill inaktivera USB-felsökning."</string>
<string name="select_input_method" msgid="4653387336791222978">"Välj inmatningsmetod"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Konfigurera inmatningsmetoder"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fysiskt tangentbord"</string>
+ <string name="hardware" msgid="7517821086888990278">"Maskinvara"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"kandidater"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Minska dagar"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Öka år"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Minska år"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"markerat"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"inte markerat"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"markerade"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ej vald"</string>
- <string name="switch_on" msgid="551417728476977311">"på"</string>
- <string name="switch_off" msgid="7249798614327155088">"av"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"intryckt"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"inte intryckt"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Avbryt"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index ab6c82f..20bc2c6 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Inaruhusu programu kubadilisha mzunguko wa skrini wakati wowote. Kamwe hazihitajiki kwa programu za kawaida."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"Badilisha kasi ya pointa"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Inaruhusu programu kubadilisha kasi ya kielekezi cha kipanya au pedi ya kufuatilia wakati wowote. Kamwe haitahitajika kwa programu za kawaida."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"badilisha mpangilio wa kibodi"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Inaruhusu programu kubadilisha mpangilio wa kibodi. Haipaswi kamwe kuhitajika kwa rogramu za kawaida."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Tuma ishara za Linux kwa programu"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Inaruhusu programu kuomba ishara iliyotolewa kutumwa kwa michakato inyoendelea."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"Fanya programu kuendeshwa kila mara"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Charaza PIN inayohitajika:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Ingiza kibambo"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Programu isiyojulikana"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Inatuma ujumbe wa SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Idadi kubwa ya jumbe za SMS inatumwa. Gusa SAWA kuendelea, au Ghairi kuwacha kutuma."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Sawa"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Ghairi"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Kadi ya SIM imeondolewa"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"mtandao wa simu hutapatika hadi uanzishe upya na SIM kadi halali iliyoingizwa."</string>
<string name="sim_done_button" msgid="827949989369963775">"Kwisha"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Gusa ili kulemaza utatuaji wa USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Chagua njia ya ingizo"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Weka mbinu za ingizo"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Kibodi halisi"</string>
+ <string name="hardware" msgid="7517821086888990278">"Maunzi"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"wagombeaji"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Punguza siku"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Ongeza mwaka"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Punguza mwaka"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"imeangaliwa"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"haijakaguliwa"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"Iliyochaguliwa"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"Haijachaguliwa"</string>
- <string name="switch_on" msgid="551417728476977311">"Washa"</string>
- <string name="switch_off" msgid="7249798614327155088">"zima"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"iliyobonyezwa"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"Haijabonyezwa"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Ghairi"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Futa"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 7007522..0884fb7 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"พิมพ์ PIN ที่ต้องการ:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"ใส่อักขระ"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"แอปพลิเคชันที่ไม่รู้จัก"</string>
<string name="sms_control_title" msgid="7296612781128917719">"กำลังส่งข้อความ SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"กำลังส่งข้อความ SMS จำนวนมาก แตะ \"ตกลง\" เพื่อทำงานต่อหรือ \"ยกเลิก\" เพื่อหยุดส่ง"</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"ตกลง"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"ยกเลิก"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"นำซิมการ์ดออกแล้ว"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"เครือข่ายมือถือจะไม่สามารถใช้งานได้จนกว่าคุณจะรีสตาร์ทโดยใส่ซิมการ์ดที่ถูกต้องแล้ว"</string>
<string name="sim_done_button" msgid="827949989369963775">"เสร็จสิ้น"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"ลดวัน"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"เพิ่มปี"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"ลดปี"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"เลือกไว้"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"ไม่ได้ตรวจสอบ"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"เลือกแล้ว"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"ไม่ได้เลือก"</string>
- <string name="switch_on" msgid="551417728476977311">"เปิด"</string>
- <string name="switch_off" msgid="7249798614327155088">"ปิด"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"กดแล้ว"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"ไม่ได้กด"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"ยกเลิก"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"ลบ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 31ad6c8..26f7d5e 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Pinapayagan ang app na baguhin ang pag-ikot ng screen anumang oras. Hindi kailanman dapat na kailanganin para sa normal na apps."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"baguhin ang bilis ng pointer"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Pinapayagan ang app na baguhin ang bilis ng mouse o trackpad pointer anumang oras. Hindi kailanman dapat na kailanganin para sa normal na apps."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"baguhin ang layout ng keyboard"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Pinapayagan ang app na baguhin ang layout ng keyboard. Hindi kailanman dapat na kailanganin para sa mga normal na app."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"magpadala ng mga signal ng Linux sa apps"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Pinapayagan ang app na hilinging maipadala ang ibinigay na signal sa lahat ng nagpapatuloy na proseso."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"palaging patakbuhin ang app"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"I-type ang kinakailangang PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Magpasok ng character"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Hindi kilalang app"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Nagpapadala ng mga SMS na mensahe"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Ipinapadala ang malaking bilang ng mga mensaheng SMS. Pindutin ang OK upang magpatuloy, o Kanselahin upang ihinto ang pagpapadala."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Kanselahin"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Naalis ang SIM card"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Hindi magiging available ang mobile network hanggang mag-restart ka gamit ang isang may-bisang SIM card"</string>
<string name="sim_done_button" msgid="827949989369963775">"Tapos na"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Pindutin upang huwag paganahin ang pag-debug ng USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Pumili ng pamamaraan ng pag-input"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"I-set up paraan ng pag-input"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Aktwal na keyboard"</string>
+ <string name="hardware" msgid="7517821086888990278">"Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"mga kandidato"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Bawasan ang araw"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Dagdagdan ang taon"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Bawasan ang taon"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"nilagyan ng check"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"hindi nilagyan ng check"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"pinili"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"Hindi pinili"</string>
- <string name="switch_on" msgid="551417728476977311">"naka-on"</string>
- <string name="switch_off" msgid="7249798614327155088">"naka-off"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"pinindot"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"hindi pinindot"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Kanselahin"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Tanggalin"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 959d8bd..8abf38d 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Uygulamaya, istediği zaman ekran dönüşünü değiştirme izni verir. Normal uygulamalar için gerekli değildir."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"işaretçi hızını değiştir"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Uygulamaya, istediği zaman fare veya izleme yüzeyi işaretçi hızını değiştirme izni verir. Normal uygulamalar için gerekli değildir."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"klavye düzenini değiştir"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Uygulamaya klavye düzenini değiştirme izni verir. Normal uygulamalar için hiçbir zaman gerekmez."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"uygulamalara Linux sinyalleri gönder"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Uygulamaya, sağlanan sinyalin tüm kalıcı işlemlere gönderilmesini isteme izni verir."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"uygulamayı her zaman çalıştır"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Gerekli PIN\'i yazın:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Karakter ekle"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Bilinmeyen uygulama"</string>
<string name="sms_control_title" msgid="7296612781128917719">"SMS mesajları gönderiliyor"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Çok sayıda SMS mesajı gönderiliyor. Devam etmek için Tamam\'a, göndermeyi durdurmak için İptal\'e dokunun."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"Tamam"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"İptal"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM kart çıkarıldı"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Geçerli bir SIM kart yerleştirilmiş olarak yeniden başlatana kadar mobil ağ kullanılamayacak."</string>
<string name="sim_done_button" msgid="827949989369963775">"Tamamlandı"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"USB hata ayıklama özelliğini devre dışı bırakmak için dokunun."</string>
<string name="select_input_method" msgid="4653387336791222978">"Giriş yöntemini seçin"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Giriş yöntemlerini ayarla"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Fiziksel klavye"</string>
+ <string name="hardware" msgid="7517821086888990278">"Donanım"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"adaylar"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Günü azalt"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Yılı artır"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Yılı azalt"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"işaretli"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"işaretlenmedi"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"seçili"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"seçili değil"</string>
- <string name="switch_on" msgid="551417728476977311">"açık"</string>
- <string name="switch_off" msgid="7249798614327155088">"kapalı"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"basıldı"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"basılmadı"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"İptal"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Sil"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index f9d372e..5504176 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Введіть потрібний PIN-код:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN-код:"</string>
<string name="select_character" msgid="3365550120617701745">"Вставл-ня символу"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Невідома програма"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Надсил. SMS повідомлень"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Надсилається велика кількість SMS повідомлень. Торкніться \"OK\", щоб продовжити, або \"Скасувати\", щоб припинити надсилання."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Скасувати"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM-карту вилучено"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Мобільна мережа буде недоступна, поки ви не здійсните перезапуск, вставивши дійсну SIM-карту."</string>
<string name="sim_done_button" msgid="827949989369963775">"Готово"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"На день назад"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"На рік уперед"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"На рік назад"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"перевірено"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"не перевірено"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"вибрано"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"не вибрано"</string>
- <string name="switch_on" msgid="551417728476977311">"увімк."</string>
- <string name="switch_off" msgid="7249798614327155088">"вимкн."</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"натиснуто"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"не натиснуто"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Скасувати"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index dde1ad4..96e97f6 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Cho phép ứng dụng thay đổi độ xoay màn hình bất cứ lúc nào. Không cần thiết cho các ứng dụng thông thường."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"thay đổi tốc độ con trỏ"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Cho phép ứng dụng thay đổi tốc độ của chuột hoặc con trỏ trên ô di chuột bất kỳ lúc nào. Không cần thiết cho các ứng dụng thông thường."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"thay đổi bố cục bàn phím"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Cho phép các ứng dụng thay đổi bố cục bàn phím. Không bao giờ cần thiết cho các ứng dụng bình thường."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"gửi tín hiệu Linux đến ứng dụng"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Cho phép ứng dụng yêu cầu tín hiệu đã cung cấp được gửi đến tất cả các quá trình liên tục."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"đặt ứng dụng luôn chạy"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Nhập PIN bắt buộc:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"Mã PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Chèn ký tự"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"Ứng dụng không xác định"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Đang gửi tin nhắn SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Một lượng lớn các tin nhắn SMS đang được gửi. Chạm OK để tiếp tục hoặc Hủy để ngừng gửi."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"OK"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Hủy"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"Đã xóa thẻ SIM"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Mạng di động sẽ không khả dụng cho đến khi bạn khởi động lại với thẻ SIM hợp lệ được lắp."</string>
<string name="sim_done_button" msgid="827949989369963775">"Xong"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Chạm để vô hiệu hóa gỡ lỗi USB."</string>
<string name="select_input_method" msgid="4653387336791222978">"Chọn phương thức nhập"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Thiết lập phương thức nhập"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Bàn phím thực"</string>
+ <string name="hardware" msgid="7517821086888990278">"Phần cứng"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"ứng viên"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Giảm ngày"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Tăng năm"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Giảm năm"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"đã kiểm tra"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"chưa chọn"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"đã chọn"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"chưa được chọn"</string>
- <string name="switch_on" msgid="551417728476977311">"bật"</string>
- <string name="switch_off" msgid="7249798614327155088">"tắt"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"đã bấm"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"chưa được bấm"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Hủy"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Xóa"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 0c338bb..08a15e4 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"允许应用随时更改屏幕的旋转状态。普通应用绝不需要此权限。"</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"更改指针速度"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"允许应用随时更改鼠标或触控板指针速度。普通应用绝不需要此权限。"</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"更改键盘布局"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"允许应用更改键盘布局。普通应用绝不需要此权限。"</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"向应用发送 Linux 信号"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"允许应用请求将提供的信号发送给所有持续的进程。"</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"让应用始终运行"</string>
@@ -1009,11 +1007,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"键入所需的 PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"插入字符"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"未知应用"</string>
<string name="sms_control_title" msgid="7296612781128917719">"正在发送短信"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"正在发送大量短信。触摸“确定”继续,或触摸“取消”停止发送。"</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"确定"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"取消"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"已移除 SIM 卡"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"移动网络不可用。请插入有效的 SIM 卡并重新启动。"</string>
<string name="sim_done_button" msgid="827949989369963775">"完成"</string>
@@ -1063,10 +1077,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"触摸以停用 USB 调试。"</string>
<string name="select_input_method" msgid="4653387336791222978">"选择输入法"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"设置输入法"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"物理键盘"</string>
+ <string name="hardware" msgid="7517821086888990278">"硬件"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"候选"</u></string>
@@ -1193,14 +1205,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"减小日期值"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"增大年份值"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"减小年份值"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"已选中"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"未选中"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"已选择"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"未选择"</string>
- <string name="switch_on" msgid="551417728476977311">"已打开"</string>
- <string name="switch_off" msgid="7249798614327155088">"已关闭"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"已按下"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"未按下"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"取消"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index c36b1b5..0468534 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1009,11 +1009,27 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"請輸入必要的 PIN:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"插入字元"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"不明的應用程式"</string>
<string name="sms_control_title" msgid="7296612781128917719">"傳送 SMS 簡訊"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"即將傳送大量的 SMS 簡訊。輕觸 [確定] 可繼續傳送,或輕觸 [取消] 停止傳送。"</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"確定"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"取消"</string>
+ <!-- no translation found for sms_control_message (3867899169651496433) -->
+ <skip />
+ <!-- no translation found for sms_control_yes (3663725993855816807) -->
+ <skip />
+ <!-- no translation found for sms_control_no (625438561395534982) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_title (1666863092640877318) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_title (3811263856304367838) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_message (5616409294907295407) -->
+ <skip />
+ <!-- no translation found for sms_premium_short_code_confirm_message (6214083016284738667) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_allow (8957573662645722940) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_deny (6374609298084435887) -->
+ <skip />
+ <!-- no translation found for sms_short_code_confirm_report (2588793956061677070) -->
+ <skip />
<string name="sim_removed_title" msgid="6227712319223226185">"SIM 卡已移除"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"您必須先插入有效的 SIM 卡再重新啟動手機,才能使用行動網路。"</string>
<string name="sim_done_button" msgid="827949989369963775">"完成"</string>
@@ -1193,14 +1209,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"減少日數"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"增加年數"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"減少年數"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"已勾選"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"尚未勾選"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"已選取"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"未選取"</string>
- <string name="switch_on" msgid="551417728476977311">"開啟"</string>
- <string name="switch_off" msgid="7249798614327155088">"關閉"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"已按下"</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"未按下"</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"Alt 鍵"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"取消"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Delete 鍵"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 3b3b1e1..c910d4a 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -286,10 +286,8 @@
<string name="permdesc_setOrientation" msgid="3046126619316671476">"Ivumela insiza ukuthi iguqule ukujikeleza kweskrini nganoma isiphi isikhathi. Akudingakeli izinsiza ezejwayelekile."</string>
<string name="permlab_setPointerSpeed" msgid="9175371613322562934">"guqula isivinini sesikhombi"</string>
<string name="permdesc_setPointerSpeed" msgid="6866563234274104233">"Ivumela insiza ukuthi iguqule ijubane legundane noma lendawo yokukhomba ngomunwe. Akufanele kudingakele izinsiza ezijwayelekile."</string>
- <!-- no translation found for permlab_setKeyboardLayout (4778731703600909340) -->
- <skip />
- <!-- no translation found for permdesc_setKeyboardLayout (8480016771134175879) -->
- <skip />
+ <string name="permlab_setKeyboardLayout" msgid="4778731703600909340">"shintsha isendlalelo sekhibhodi"</string>
+ <string name="permdesc_setKeyboardLayout" msgid="8480016771134175879">"Ivumela uhlelo lokusebenza ukushintsha isendlalelo sekhibhodi. Kufanele ingadingi izinhlelo zokusebenzia ezivamile."</string>
<string name="permlab_signalPersistentProcesses" msgid="4539002991947376659">"Thumela imifanekiso ye-Linu ezinsizeni"</string>
<string name="permdesc_signalPersistentProcesses" msgid="4896992079182649141">"Ivumela insiza ukuthi icele ukuthi isiginali ethunyelwe idluliselwe kuzo zonke izinqubeko ezisalelayo."</string>
<string name="permlab_persistentActivity" msgid="8841113627955563938">"yenza insiza ukuthi ihlale isebenza"</string>
@@ -1009,11 +1007,17 @@
<string name="wifi_p2p_enter_pin_message" msgid="5920929550367828970">"Faka i-PIN edingekayo:"</string>
<string name="wifi_p2p_show_pin_message" msgid="8530563323880921094">"PIN:"</string>
<string name="select_character" msgid="3365550120617701745">"Faka uhlamvu"</string>
- <string name="sms_control_default_app_name" msgid="3058577482636640465">"insiza engaziwa"</string>
<string name="sms_control_title" msgid="7296612781128917719">"Ithumela imiyalezo ye-SMS"</string>
- <string name="sms_control_message" msgid="4073755190243093924">"Inani eliphezulu lwama-SMS liyathunyelwa. Khetha \"KULUNGILE\" ukuqhubeka, noma \"Khansela\" ukumisa ukuthumela."</string>
- <string name="sms_control_yes" msgid="2532062172402615953">"KULUNGILE"</string>
- <string name="sms_control_no" msgid="1715320703137199869">"Khansela"</string>
+ <string name="sms_control_message" msgid="3867899169651496433">"I-<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ithumela inombolo enkulu yemilayezo ye-SMS. Ufuna ukuvumela lolu hlelo lokusebenza ukuqhubeka ukuthumela imilayezo?"</string>
+ <string name="sms_control_yes" msgid="3663725993855816807">"Vumela"</string>
+ <string name="sms_control_no" msgid="625438561395534982">"Nqaba"</string>
+ <string name="sms_short_code_confirm_title" msgid="1666863092640877318">"Thumela ikhodi efushane?"</string>
+ <string name="sms_premium_short_code_confirm_title" msgid="3811263856304367838">"Ukuthumela i-SMS ye-premium?"</string>
+ <string name="sms_short_code_confirm_message" msgid="5616409294907295407">"I-<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ithanda ukuthumela umlayezo wombhalo ku-<b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, okubonakala sengathi ikhodi ye-SMS efushane.<p>Ukuthumela umlayezo wombhalo kungabangela i-akhawunti yeselula yakho ukuthi ikhokheliswe amasevisi e-premium.<p>Ufuna ukuvumela uhlelo lokusebenza ukuthumela umlayezo?"</string>
+ <string name="sms_premium_short_code_confirm_message" msgid="6214083016284738667">"I-<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ithanda ukuthumela umlayezo wombhalo ku-<b><xliff:g id="DEST_ADDRESS">%2$s</xliff:g></b>, okuyikhodi efushane ye-SMS ye-premium.<p><b>Ukuthumela umlayezo kule ndawo kuzobangela i-akhawunti yeselula yakho ukuthi ikhokheliswe amasevisi e-premium.</b><p>Ufuna ukuvumela lolu hlelo lokusebenza ukuthumela umlayezo?"</string>
+ <string name="sms_short_code_confirm_allow" msgid="8957573662645722940">"Thumela umlayezo?"</string>
+ <string name="sms_short_code_confirm_deny" msgid="6374609298084435887">"Ungathumeli"</string>
+ <string name="sms_short_code_confirm_report" msgid="2588793956061677070">"Bika uhlelo lokusebenza olungalungile"</string>
<string name="sim_removed_title" msgid="6227712319223226185">"Ikhadi le-SIM likhishiwe"</string>
<string name="sim_removed_message" msgid="2333164559970958645">"Inethiwekhi yeselula ngeke itholakale kuwena kuze kube uqala kabusha ufake ikhadi le-SIM elifanele."</string>
<string name="sim_done_button" msgid="827949989369963775">"Kwenziwe"</string>
@@ -1063,10 +1067,8 @@
<string name="adb_active_notification_message" msgid="1016654627626476142">"Thinta ukwenza ukuthi ukudibhaga kwe-USB kungasebenzi."</string>
<string name="select_input_method" msgid="4653387336791222978">"Khetha indlela yokufaka"</string>
<string name="configure_input_methods" msgid="9091652157722495116">"Izilungiselelo zezindlela zokufakwayo"</string>
- <!-- no translation found for use_physical_keyboard (6203112478095117625) -->
- <skip />
- <!-- no translation found for hardware (7517821086888990278) -->
- <skip />
+ <string name="use_physical_keyboard" msgid="6203112478095117625">"Ukwakheka kwekhibhodi"</string>
+ <string name="hardware" msgid="7517821086888990278">"I-Hardware"</string>
<string name="fast_scroll_alphabet" msgid="5433275485499039199">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="fast_scroll_numeric_alphabet" msgid="4030170524595123610">" 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
<string name="candidates_style" msgid="4333913089637062257"><u>"abahlanganyeli"</u></string>
@@ -1193,14 +1195,6 @@
<string name="date_picker_decrement_day_button" msgid="4131881521818750031">"Yehlisa usuku"</string>
<string name="date_picker_increment_year_button" msgid="6318697384310808899">"Khulisa unyaka"</string>
<string name="date_picker_decrement_year_button" msgid="4482021813491121717">"Yehlisa unyaka"</string>
- <string name="checkbox_checked" msgid="7222044992652711167">"kuhloliwe"</string>
- <string name="checkbox_not_checked" msgid="5174639551134444056">"akuhloliwe"</string>
- <string name="radiobutton_selected" msgid="8603599808486581511">"Okukhethiwe"</string>
- <string name="radiobutton_not_selected" msgid="2908760184307722393">"akukhethiwe"</string>
- <string name="switch_on" msgid="551417728476977311">"vuliwe"</string>
- <string name="switch_off" msgid="7249798614327155088">"valiwe"</string>
- <string name="togglebutton_pressed" msgid="4180411746647422233">"kucindezelwe."</string>
- <string name="togglebutton_not_pressed" msgid="4495147725636134425">"akucindezelwe."</string>
<string name="keyboardview_keycode_alt" msgid="4856868820040051939">"i-ALT"</string>
<string name="keyboardview_keycode_cancel" msgid="1203984017245783244">"Khansela"</string>
<string name="keyboardview_keycode_delete" msgid="3337914833206635744">"Susa"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index aabe407..de24d10 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -835,6 +835,10 @@
<!-- Reference to the Pointer style -->
<attr name="pointerStyle" format="reference" />
+
+ <!-- The drawable for accessibility focused views. -->
+ <attr name="accessibilityFocusedDrawable" format="reference" />
+
</declare-styleable>
<!-- **************************************************************** -->
@@ -2123,6 +2127,20 @@
layoutDirection is LTR, and ALIGN_LEFT otherwise -->
<enum name="viewEnd" value="6" />
</attr>
+
+ <!-- Controls how this View is important for accessibility which is if it fires
+ accessibility events and if it is reported to accessibility services that
+ query the screen. Note: While not recommended, an accessibility service may
+ decide to ignore this attribute and operate on all views in the view tree. -->
+ <attr name="importantForAccessibility" format="integer">
+ <!-- The system determines whether the view is important for accessibility (recommended). -->
+ <enum name="auto" value="0" />
+ <!-- The view is important for accessibility. -->
+ <enum name="yes" value="1" />
+ <!-- The view is not important for accessibility. -->
+ <enum name="no" value="2" />
+ </attr>
+
</declare-styleable>
<!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
@@ -2445,6 +2463,8 @@
<attr name="accessibilityFlags">
<!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#DEFAULT} -->
<flag name="flagDefault" value="0x00000001" />
+ <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#INCLUDE_NOT_IMPORTANT_VIEWS} -->
+ <flag name="flagIncludeNotImportantViews" value="0x00000002" />
</attr>
<!-- Component name of an activity that allows the user to modify
the settings for this service. This setting cannot be changed at runtime. -->
@@ -4317,6 +4337,7 @@
<li>"state_hovered"
<li>"state_drag_can_accept"
<li>"state_drag_hovered"
+ <li>"state_accessibility_focused"
</ul> -->
<declare-styleable name="DrawableStates">
<!-- State value for {@link android.graphics.drawable.StateListDrawable StateListDrawable},
@@ -4377,6 +4398,9 @@
indicating that a drag operation (for which the Drawable's view is a valid recipient)
is currently positioned over the Drawable. -->
<attr name="state_drag_hovered" format="boolean" />
+ <!-- State for {@link android.graphics.drawable.StateListDrawable StateListDrawable}
+ indicating that a View has accessibility focus. -->
+ <attr name="state_accessibility_focused" format="boolean" />
</declare-styleable>
<declare-styleable name="ViewDrawableStates">
<attr name="state_pressed" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index aaef701..4a5e442 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -226,6 +226,7 @@
<java-symbol type="attr" name="windowFixedWidthMinor" />
<java-symbol type="attr" name="windowFixedHeightMajor" />
<java-symbol type="attr" name="windowFixedHeightMinor" />
+ <java-symbol type="attr" name="accessibilityFocusedDrawable"/>
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
@@ -400,8 +401,6 @@
<java-symbol type="string" name="cfTemplateNotForwarded" />
<java-symbol type="string" name="cfTemplateRegistered" />
<java-symbol type="string" name="cfTemplateRegisteredTime" />
- <java-symbol type="string" name="checkbox_checked" />
- <java-symbol type="string" name="checkbox_not_checked" />
<java-symbol type="string" name="chooseActivity" />
<java-symbol type="string" name="config_default_dns_server" />
<java-symbol type="string" name="config_ethernet_iface_regex" />
@@ -701,8 +700,6 @@
<java-symbol type="string" name="preposition_for_time" />
<java-symbol type="string" name="progress_erasing" />
<java-symbol type="string" name="progress_unmounting" />
- <java-symbol type="string" name="radiobutton_not_selected" />
- <java-symbol type="string" name="radiobutton_selected" />
<java-symbol type="string" name="relationTypeAssistant" />
<java-symbol type="string" name="relationTypeBrother" />
<java-symbol type="string" name="relationTypeChild" />
@@ -791,14 +788,18 @@
<java-symbol type="string" name="sipAddressTypeHome" />
<java-symbol type="string" name="sipAddressTypeOther" />
<java-symbol type="string" name="sipAddressTypeWork" />
- <java-symbol type="string" name="sms_control_default_app_name" />
<java-symbol type="string" name="sms_control_message" />
- <java-symbol type="string" name="sms_control_no" />
<java-symbol type="string" name="sms_control_title" />
+ <java-symbol type="string" name="sms_control_no" />
<java-symbol type="string" name="sms_control_yes" />
+ <java-symbol type="string" name="sms_premium_short_code_confirm_message" />
+ <java-symbol type="string" name="sms_premium_short_code_confirm_title" />
+ <java-symbol type="string" name="sms_short_code_confirm_allow" />
+ <java-symbol type="string" name="sms_short_code_confirm_deny" />
+ <java-symbol type="string" name="sms_short_code_confirm_message" />
+ <java-symbol type="string" name="sms_short_code_confirm_report" />
+ <java-symbol type="string" name="sms_short_code_confirm_title" />
<java-symbol type="string" name="submit" />
- <java-symbol type="string" name="switch_off" />
- <java-symbol type="string" name="switch_on" />
<java-symbol type="string" name="sync_binding_label" />
<java-symbol type="string" name="sync_do_nothing" />
<java-symbol type="string" name="sync_really_delete" />
@@ -820,8 +821,6 @@
<java-symbol type="string" name="time_wday" />
<java-symbol type="string" name="time_wday_date" />
<java-symbol type="string" name="today" />
- <java-symbol type="string" name="togglebutton_not_pressed" />
- <java-symbol type="string" name="togglebutton_pressed" />
<java-symbol type="string" name="tomorrow" />
<java-symbol type="string" name="twelve_hour_time_format" />
<java-symbol type="string" name="twenty_four_hour_time_format" />
@@ -1103,7 +1102,9 @@
<java-symbol type="xml" name="password_kbd_symbols_shift" />
<java-symbol type="xml" name="power_profile" />
<java-symbol type="xml" name="time_zones_by_country" />
+ <java-symbol type="xml" name="sms_short_codes" />
+ <java-symbol type="raw" name="accessibility_gestures" />
<java-symbol type="raw" name="incognito_mode_start_page" />
<java-symbol type="raw" name="loaderror" />
<java-symbol type="raw" name="nodomain" />
@@ -1133,6 +1134,7 @@
<!-- From android.policy -->
<java-symbol type="anim" name="app_starting_exit" />
<java-symbol type="anim" name="lock_screen_behind_enter" />
+ <java-symbol type="anim" name="lock_screen_wallpaper_behind_enter" />
<java-symbol type="anim" name="dock_top_enter" />
<java-symbol type="anim" name="dock_top_exit" />
<java-symbol type="anim" name="dock_bottom_enter" />
@@ -3583,7 +3585,11 @@
<public type="attr" name="layout_marginEnd"/>
<public type="attr" name="kcm"/>
+
<public type="attr" name="parentActivityName" />
<public type="attr" name="supportsSentenceSpellCheck" />
+
+ <public type="attr" name="importantForAccessibility"/>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0eb46bd..e00986c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2789,16 +2789,30 @@
<string name="select_character">Insert character</string>
<!-- SMS per-application rate control Dialog --> <skip />
- <!-- See SMS_DIALOG. This is shown if the current application's name cannot be figuerd out. -->
- <string name="sms_control_default_app_name">Unknown app</string>
<!-- SMS_DIALOG: An SMS dialog is shown if an application tries to send too many SMSes. This is the title of that dialog. -->
<string name="sms_control_title">Sending SMS messages</string>
- <!-- See SMS_DIALOG. This is the message shown in that dialog. -->
- <string name="sms_control_message">A large number of SMS messages are being sent. Touch OK to continue, or Cancel to stop sending.</string>
- <!-- See SMS_DIALOG. This is a button choice to allow sending the SMSes. -->
- <string name="sms_control_yes">OK</string>
- <!-- See SMS_DIALOG. This is a button choice to disallow sending the SMSes.. -->
- <string name="sms_control_no">Cancel</string>
+ <!-- See SMS_DIALOG. This is the message shown in that dialog. [CHAR LIMIT=NONE] -->
+ <string name="sms_control_message"><b><xliff:g id="app_name">%1$s</xliff:g></b> is sending a large number of SMS messages. Do you want to allow this app to continue sending messages?</string>
+ <!-- See SMS_DIALOG. This is a button choice to allow sending the SMSes. [CHAR LIMIT=30] -->
+ <string name="sms_control_yes">Allow</string>
+ <!-- See SMS_DIALOG. This is a button choice to disallow sending the SMSes. [CHAR LIMIT=30] -->
+ <string name="sms_control_no">Deny</string>
+
+ <!-- SMS short code verification dialog. --> <skip />
+ <!-- The dialog title for the SMS short code confirmation dialog. [CHAR LIMIT=30] -->
+ <string name="sms_short_code_confirm_title">Send SMS to short code?</string>
+ <!-- The dialog title for the SMS premium short code confirmation dialog. [CHAR LIMIT=30] -->
+ <string name="sms_premium_short_code_confirm_title">Send premium SMS?</string>
+ <!-- The message text for the SMS short code confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="sms_short_code_confirm_message"><b><xliff:g id="app_name">%1$s</xliff:g></b> would like to send a text message to <b><xliff:g id="dest_address">%2$s</xliff:g></b>, which appears to be an SMS short code.<p>Sending text messages to some short codes may cause your mobile account to be billed for premium services.<p>Do you want to allow this app to send the message?</string>
+ <!-- The message text for the SMS short code confirmation dialog. [CHAR LIMIT=NONE] -->
+ <string name="sms_premium_short_code_confirm_message"><b><xliff:g id="app_name">%1$s</xliff:g></b> would like to send a text message to <b><xliff:g id="dest_address">%2$s</xliff:g></b>, which is a premium SMS short code.<p><b>Sending a message to this destination will cause your mobile account to be billed for premium services.</b><p>Do you want to allow this app to send the message?</string>
+ <!-- Text of the approval button for the SMS short code confirmation dialog. [CHAR LIMIT=50] -->
+ <string name="sms_short_code_confirm_allow">Send message</string>
+ <!-- Text of the cancel button for the SMS short code confirmation dialog. [CHAR LIMIT=30] -->
+ <string name="sms_short_code_confirm_deny">Don\'t send</string>
+ <!-- Text of the button for the SMS short code confirmation dialog to report a malicious app. [CHAR LIMIT=30] -->
+ <string name="sms_short_code_confirm_report">Report malicious app</string>
<!-- SIM swap and device reboot Dialog --> <skip />
<!-- See SIM_REMOVED_DIALOG. This is the title of that dialog. -->
@@ -3241,30 +3255,6 @@
<!-- Description of the button to decrease the DatePicker's year value. [CHAR LIMIT=NONE] -->
<string name="date_picker_decrement_year_button">Decrease year</string>
- <!-- CheckBox - accessibility support -->
- <!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] -->
- <string name="checkbox_checked">checked</string>
- <!-- Description of the not checked state of a CheckBox. [CHAR LIMIT=NONE] -->
- <string name="checkbox_not_checked">not checked</string>
-
- <!-- RadioButton/CheckedTextView - accessibility support -->
- <!-- Description of the selected state of a RadioButton. [CHAR LIMIT=NONE] -->
- <string name="radiobutton_selected">selected</string>
- <!-- Description of the not selected state of a RadioButton. [CHAR LIMIT=NONE] -->
- <string name="radiobutton_not_selected">not selected</string>
-
- <!-- Switch - accessibility support -->
- <!-- Description of the on state of a Switch. [CHAR LIMIT=NONE] -->
- <string name="switch_on">on</string>
- <!-- Description of the off state of a Switch. [CHAR LIMIT=NONE] -->
- <string name="switch_off">off</string>
-
- <!-- ToggleButton - accessibility support -->
- <!-- Description of the pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
- <string name="togglebutton_pressed">pressed</string>
- <!-- Description of the not pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
- <string name="togglebutton_not_pressed">not pressed</string>
-
<!-- KeyboardView - accessibility support -->
<!-- Description of the Alt button in a KeyboardView. [CHAR LIMIT=NONE] -->
<string name="keyboardview_keycode_alt">Alt</string>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 7e06e24..71738ad 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -366,6 +366,9 @@
<!-- Pointer style -->
<item name="pointerStyle">@android:style/Pointer</item>
+
+ <!-- Accessibility focused drawable. -->
+ <item name="accessibilityFocusedDrawable">@android:drawable/view_accessibility_focused</item>
</style>
<!-- Variant of {@link #Theme} with no title bar -->
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
new file mode 100644
index 0000000..8b395af
--- /dev/null
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -0,0 +1,189 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+** 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.
+*/
+-->
+
+<!-- Regex patterns for SMS short codes by country. -->
+<shortcodes>
+
+ <!-- The country attribute is the ISO country code of the user's account (from SIM card or NV).
+ The pattern attribute is a regex that matches all SMS short codes for the country.
+ The premium attribute is a regex that matches premium rate SMS short codes.
+ The free attribute matches short codes that we know will not cost the user, such as
+ emergency numbers. The standard attribute matches short codes that are billed at the
+ standard SMS rate. The user is warned when the destination phone number matches the
+ "pattern" or "premium" regexes, and does not match the "free" or "standard" regexes. -->
+
+ <!-- Harmonised European Short Codes are 6 digit numbers starting with 116 (free helplines).
+ Premium patterns include short codes from: http://aonebill.com/coverage&tariffs
+ and http://mobilcent.com/info-worldwide.asp and extracted from:
+ http://smscoin.net/software/engine/WordPress/Paid+SMS-registration/ -->
+
+ <!-- Albania: 5 digits, known short codes listed -->
+ <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" />
+
+ <!-- Armenia: 3-4 digits, emergency numbers 10[123] -->
+ <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" />
+
+ <!-- Austria: 10 digits, premium prefix 09xx, plus EU -->
+ <shortcode country="at" pattern="11\\d{4}" premium="09.*" free="116\\d{3}" />
+
+ <!-- Australia: 6 or 8 digits starting with "19" -->
+ <shortcode country="au" pattern="19(?:\\d{4}|\\d{6})" premium="19998882" />
+
+ <!-- Azerbaijan: 4-5 digits, known premium codes listed -->
+ <shortcode country="az" pattern="\\d{4,5}" premium="330[12]|87744|901[234]|93(?:94|101)|9426|9525" />
+
+ <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp -->
+ <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" />
+
+ <!-- Bulgaria: 4-5 digits, plus EU -->
+ <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}" />
+
+ <!-- Belarus: 4 digits -->
+ <shortcode country="by" pattern="\\d{4}" premium="3336|4161|444[4689]|501[34]|7781" />
+
+ <!-- Canada: 5-6 digits -->
+ <shortcode country="ca" pattern="\\d{5,6}" premium="60999|88188" />
+
+ <!-- Switzerland: 3-5 digits: http://www.swisscom.ch/fxres/kmu/thirdpartybusiness_code_of_conduct_en.pdf -->
+ <shortcode country="ch" pattern="[2-9]\\d{2,4}" premium="543|83111" />
+
+ <!-- China: premium shortcodes start with "1066", free shortcodes start with "1065":
+ http://clients.txtnation.com/entries/197192-china-premium-sms-short-code-requirements -->
+ <shortcode country="cn" premium="1066.*" free="1065.*" />
+
+ <!-- Cyprus: 4-6 digits (not confirmed), known premium codes listed, plus EU -->
+ <shortcode country="cy" pattern="\\d{4,6}" premium="7510" free="116\\d{3}" />
+
+ <!-- Czech Republic: 7-8 digits, starting with 9, plus EU:
+ http://www.o2.cz/osobni/en/services-by-alphabet/91670-premium_sms.html -->
+ <shortcode country="cz" premium="9\\d{6,7}" free="116\\d{3}" />
+
+ <!-- Germany: 4-5 digits plus 1232xxx (premium codes from http://www.vodafone.de/infofaxe/537.pdf and http://premiumdienste.eplus.de/pdf/kodex.pdf), plus EU. To keep the premium regex from being too large, it only includes payment processors that have been used by SMS malware, with the regular pattern matching the other premium short codes. -->
+ <shortcode country="de" pattern="\\d{4,5}|1232\\d{3}" premium="11(?:111|833)|1232(?:013|021|060|075|286|358)|118(?:44|80|86)|20[25]00|220(?:21|22|88|99)|221(?:14|21)|223(?:44|53|77)|224[13]0|225(?:20|59|90)|226(?:06|10|20|26|30|40|56|70)|227(?:07|33|39|66|76|78|79|88|99)|228(?:08|11|66|77)|23300|30030|3[12347]000|330(?:33|55|66)|33(?:233|331|366|533)|34(?:34|567)|37000|40(?:040|123|444|[3568]00)|41(?:010|414)|44(?:000|044|344|44[24]|544)|50005|50100|50123|50555|51000|52(?:255|783)|54(?:100|2542)|55(?:077|[24]00|222|333|55|[12369]55)|56(?:789|886)|60800|6[13]000|66(?:[12348]66|566|766|777|88|999)|68888|70(?:07|123|777)|76766|77(?:007|070|222|444|[567]77)|80(?:008|123|888)|82(?:002|[378]00|323|444|472|474|488|727)|83(?:005|[169]00|333|830)|84(?:141|300|32[34]|343|488|499|777|888)|85888|86(?:188|566|640|644|650|677|868|888)|870[24]9|871(?:23|[49]9)|872(?:1[0-8]|49|99)|87499|875(?:49|55|99)|876(?:0[1367]|1[1245678]|54|99)|877(?:00|99)|878(?:15|25|3[567]|8[12])|87999|880(?:08|44|55|77|99)|88688|888(?:03|10|8|89)|8899|90(?:009|999)|99999" free="116\\d{3}" />
+
+ <!-- Denmark: see http://iprs.webspacecommerce.com/Denmark-Premium-Rate-Numbers -->
+ <shortcode country="dk" pattern="\\d{4,5}" premium="1\\d{3}" free="116\\d{3}" />
+
+ <!-- Estonia: short codes 3-5 digits starting with 1, plus premium 7 digit numbers starting with 90, plus EU.
+ http://www.tja.ee/public/documents/Elektrooniline_side/Oigusaktid/ENG/Estonian_Numbering_Plan_annex_06_09_2010.mht -->
+ <shortcode country="ee" pattern="1\\d{2,4}" premium="90\\d{5}|15330|1701[0-3]" free="116\\d{3}" />
+
+ <!-- Spain: 5-6 digits: 25xxx, 27xxx, 280xx, 35xxx, 37xxx, 795xxx, 797xxx, 995xxx, 997xxx, plus EU.
+ http://www.legallink.es/?q=en/content/which-current-regulatory-status-premium-rate-services-spain -->
+ <shortcode country="es" premium="[23][57]\\d{3}|280\\d{2}|[79]9[57]\\d{3}" free="116\\d{3}" />
+
+ <!-- Finland: 5-6 digits, premium 0600, 0700: http://en.wikipedia.org/wiki/Telephone_numbers_in_Finland -->
+ <shortcode country="fi" pattern="\\d{5,6}" premium="0600.*|0700.*|171(?:59|63)" free="116\\d{3}" />
+
+ <!-- France: 5 digits, free: 3xxxx, premium [4-8]xxxx, plus EU:
+ http://clients.txtnation.com/entries/161972-france-premium-sms-short-code-requirements -->
+ <shortcode country="fr" premium="[4-8]\\d{4}" free="3\\d{4}|116\\d{3}" />
+
+ <!-- United Kingdom (Great Britain): 4-6 digits, common codes [5-8]xxxx, plus EU:
+ http://www.short-codes.com/media/Co-regulatoryCodeofPracticeforcommonshortcodes170206.pdf -->
+ <shortcode country="gb" pattern="\\d{4,6}" premium="[5-8]\\d{4}" free="116\\d{3}" />
+
+ <!-- Georgia: 4 digits, known premium codes listed -->
+ <shortcode country="ge" pattern="\\d{4}" premium="801[234]|888[239]" />
+
+ <!-- Greece: 5 digits (54xxx, 19yxx, x=0-9, y=0-5): http://www.cmtelecom.com/premium-sms/greece -->
+ <shortcode country="gr" pattern="\\d{5}" premium="54\\d{3}|19[0-5]\\d{2}" free="116\\d{3}" />
+
+ <!-- Hungary: 4 or 10 digits starting with 1 or 0, plus EU:
+ http://clients.txtnation.com/entries/209633-hungary-premium-sms-short-code-regulations -->
+ <shortcode country="hu" pattern="[01](?:\\d{3}|\\d{9})" premium="0691227910|1784" free="116\\d{3}" />
+
+ <!-- Ireland: 5 digits, 5xxxx (50xxx=free, 5[12]xxx=standard), plus EU:
+ http://www.comreg.ie/_fileupload/publications/ComReg1117.pdf -->
+ <shortcode country="ie" pattern="\\d{5}" premium="5[3-9]\\d{3}" free="50\\d{3}|116\\d{3}" standard="5[12]\\d{3}" />
+
+ <!-- Israel: 4 digits, known premium codes listed -->
+ <shortcode country="il" pattern="\\d{4}" premium="4422|4545" />
+
+ <!-- Italy: 5 digits (premium=4xxxx), plus EU:
+ http://clients.txtnation.com/attachments/token/di5kfblvubttvlw/?name=Italy_CASP_EN.pdf -->
+ <shortcode country="it" pattern="\\d{5}" premium="4\\d{4}" free="116\\d{3}" />
+
+ <!-- Kyrgyzstan: 4 digits, known premium codes listed -->
+ <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" />
+
+ <!-- Kazakhstan: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-kazakhstan/ -->
+ <shortcode country="kz" pattern="\\d{4}" premium="335[02]|4161|444[469]|77[2359]0|8444|919[3-5]|968[2-5]" />
+
+ <!-- Lithuania: 3-5 digits, known premium codes listed, plus EU -->
+ <shortcode country="lt" pattern="\\d{3,5}" premium="13[89]1|1394|16[34]5" free="116\\d{3}" />
+
+ <!-- Luxembourg: 5 digits, 6xxxx, plus EU:
+ http://www.luxgsm.lu/assets/files/filepage/file_1253803400.pdf -->
+ <shortcode country="lu" premium="6\\d{4}" free="116\\d{3}" />
+
+ <!-- Latvia: 4 digits, known premium codes listed, plus EU -->
+ <shortcode country="lv" pattern="\\d{4}" premium="18(?:19|63|7[1-4])" free="116\\d{3}" />
+
+ <!-- Mexico: 4-5 digits (not confirmed), known premium codes listed -->
+ <shortcode country="mx" pattern="\\d{4,5}" premium="53035|7766" />
+
+ <!-- Malaysia: 5 digits: http://www.skmm.gov.my/attachment/Consumer_Regulation/Mobile_Content_Services_FAQs.pdf -->
+ <shortcode country="my" pattern="\\d{5}" premium="32298|33776" />
+
+ <!-- The Netherlands, 4 digits, known premium codes listed, plus EU -->
+ <shortcode country="nl" pattern="\\d{4}" premium="4466|5040" free="116\\d{3}" />
+
+ <!-- Norway: 4-5 digits (not confirmed), known premium codes listed -->
+ <shortcode country="no" pattern="\\d{4,5}" premium="2201|222[67]" />
+
+ <!-- New Zealand: 3-4 digits, known premium codes listed -->
+ <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995" />
+
+ <!-- Poland: 4-5 digits (not confirmed), known premium codes listed, plus EU -->
+ <shortcode country="pl" pattern="\\d{4,5}" premium="74240|79(?:10|866)|92525" free="116\\d{3}" />
+
+ <!-- Portugal: 5 digits, plus EU:
+ http://clients.txtnation.com/entries/158326-portugal-premium-sms-short-code-regulations -->
+ <shortcode country="pt" premium="6[1289]\\d{3}" free="116\\d{3}" />
+
+ <!-- Romania: 4 digits, plus EU: http://www.simplus.ro/en/resources/glossary-of-terms/ -->
+ <shortcode country="ro" pattern="\\d{4}" premium="12(?:63|66|88)|13(?:14|80)" free="116\\d{3}" />
+
+ <!-- Russia: 4 digits, known premium codes listed: http://smscoin.net/info/pricing-russia/ -->
+ <shortcode country="ru" pattern="\\d{4}" premium="1(?:1[56]1|899)|2(?:09[57]|322|47[46]|880|990)|3[589]33|4161|44(?:4[3-9]|81)|77(?:33|81)" />
+
+ <!-- Sweden: 5 digits (72xxx), plus EU: http://www.viatel.se/en/premium-sms/ -->
+ <shortcode country="se" premium="72\\d{3}" free="116\\d{3}" />
+
+ <!-- Singapore: 5 digits: http://clients.txtnation.com/entries/306442-singapore-premium-sms-short-code-requirements
+ Free government directory info at 74688: http://app.sgdi.gov.sg/sms_help.asp -->
+ <shortcode country="sg" pattern="7\\d{4}" premium="73800" standard="74688" />
+
+ <!-- Slovenia: 4 digits (premium=3xxx, 6xxx, 8xxx), plus EU: http://www.cmtelecom.com/premium-sms/slovenia -->
+ <shortcode country="si" pattern="\\d{4}" premium="[368]\\d{3}" free="116\\d{3}" />
+
+ <!-- Slovakia: 4 digits (premium), plus EU: http://www.cmtelecom.com/premium-sms/slovakia -->
+ <shortcode country="sk" premium="\\d{4}" free="116\\d{3}" />
+
+ <!-- Tajikistan: 4 digits, known premium codes listed -->
+ <shortcode country="tj" pattern="\\d{4}" premium="11[3-7]1|4161|4333|444[689]" />
+
+ <!-- Ukraine: 4 digits, known premium codes listed -->
+ <shortcode country="ua" pattern="\\d{4}" premium="444[3-9]|70[579]4|7540" />
+
+ <!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm) -->
+ <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" />
+
+</shortcodes>
diff --git a/core/tests/coretests/apks/install/Android.mk b/core/tests/coretests/apks/install/Android.mk
new file mode 100644
index 0000000..b38dc20
--- /dev/null
+++ b/core/tests/coretests/apks/install/Android.mk
@@ -0,0 +1,8 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := install
+
+include $(FrameworkCoreTests_BUILD_PACKAGE)
diff --git a/core/tests/coretests/apks/install/AndroidManifest.xml b/core/tests/coretests/apks/install/AndroidManifest.xml
new file mode 100644
index 0000000..60f1ba0
--- /dev/null
+++ b/core/tests/coretests/apks/install/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.frameworks.coretests.install_loc">
+
+ <application android:hasCode="false">
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install/res/values/strings.xml b/core/tests/coretests/apks/install/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/res/raw/install b/core/tests/coretests/res/raw/install
deleted file mode 100644
index 06981f4..0000000
--- a/core/tests/coretests/res/raw/install
+++ /dev/null
Binary files differ
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index 9575ced..580b4da 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -170,11 +170,10 @@
return ipm;
}
- public boolean invokeInstallPackage(Uri packageURI, int flags, GenericReceiver receiver) {
+ public void invokeInstallPackage(Uri packageURI, int flags, GenericReceiver receiver,
+ boolean shouldSucceed) {
PackageInstallObserver observer = new PackageInstallObserver();
- final boolean received = false;
mContext.registerReceiver(receiver, receiver.filter);
- final boolean DEBUG = true;
try {
// Wait on observer
synchronized(observer) {
@@ -192,10 +191,24 @@
if(!observer.isDone()) {
fail("Timed out waiting for packageInstalled callback");
}
- if (observer.returnCode != PackageManager.INSTALL_SUCCEEDED) {
- Log.i(TAG, "Failed to install with error code = " + observer.returnCode);
- return false;
+
+ if (shouldSucceed) {
+ if (observer.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+ fail("Package installation should have succeeded, but got code "
+ + observer.returnCode);
+ }
+ } else {
+ if (observer.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ fail("Package installation should fail");
+ }
+
+ /*
+ * We'll never expect get a notification since we
+ * shouldn't succeed.
+ */
+ return;
}
+
// Verify we received the broadcast
waitTime = 0;
while((!receiver.isDone()) && (waitTime < MAX_WAIT_TIME) ) {
@@ -209,7 +222,6 @@
if(!receiver.isDone()) {
fail("Timed out waiting for PACKAGE_ADDED notification");
}
- return receiver.received;
}
}
} finally {
@@ -588,7 +600,7 @@
}
} else {
InstallReceiver receiver = new InstallReceiver(pkg.packageName);
- assertTrue(invokeInstallPackage(packageURI, flags, receiver));
+ invokeInstallPackage(packageURI, flags, receiver, true);
// Verify installed information
assertInstall(pkg, flags, expInstallLocation);
}
@@ -705,13 +717,9 @@
receiver = new InstallReceiver(ip.pkg.packageName);
}
try {
- try {
- assertEquals(invokeInstallPackage(ip.packageURI, flags, receiver), replace);
- if (replace) {
- assertInstall(ip.pkg, flags, ip.pkg.installLocation);
- }
- } catch (Exception e) {
- failStr("Failed with exception : " + e);
+ invokeInstallPackage(ip.packageURI, flags, receiver, replace);
+ if (replace) {
+ assertInstall(ip.pkg, flags, ip.pkg.installLocation);
}
} finally {
cleanUpInstall(ip);
@@ -1244,7 +1252,7 @@
GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
try {
- assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags, receiver), true);
+ invokeInstallPackage(ip.packageURI, replaceFlags, receiver, true);
assertInstall(ip.pkg, rFlags, ip.pkg.installLocation);
} catch (Exception e) {
failStr("Failed with exception : " + e);
@@ -1271,7 +1279,7 @@
GenericReceiver receiver = new ReplaceReceiver(ip.pkg.packageName);
int replaceFlags = rFlags | PackageManager.INSTALL_REPLACE_EXISTING;
try {
- assertEquals(invokeInstallPackage(ip.packageURI, replaceFlags, receiver), true);
+ invokeInstallPackage(ip.packageURI, replaceFlags, receiver, true);
assertInstall(ip.pkg, iFlags, ip.pkg.installLocation);
} catch (Exception e) {
failStr("Failed with exception : " + e);
diff --git a/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java b/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java
index 3dba4e5..1968a32 100644
--- a/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java
+++ b/core/tests/coretests/src/android/widget/focus/ListOfButtonsTest.java
@@ -19,7 +19,7 @@
import android.widget.focus.ListOfButtons;
import com.android.frameworks.coretests.R;
-import android.test.ActivityInstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import android.widget.ListAdapter;
import android.widget.Button;
@@ -31,7 +31,7 @@
* Tests that focus works as expected when navigating into and out of
* a {@link ListView} that has buttons in it.
*/
-public class ListOfButtonsTest extends ActivityInstrumentationTestCase<ListOfButtons> {
+public class ListOfButtonsTest extends ActivityInstrumentationTestCase2<ListOfButtons> {
private ListAdapter mListAdapter;
private Button mButtonAtTop;
@@ -39,7 +39,7 @@
private ListView mListView;
public ListOfButtonsTest() {
- super("com.android.frameworks.coretests", ListOfButtons.class);
+ super(ListOfButtons.class);
}
@Override
@@ -47,6 +47,7 @@
super.setUp();
ListOfButtons a = getActivity();
+ getInstrumentation().waitForIdleSync();
mListAdapter = a.getListAdapter();
mButtonAtTop = (Button) a.findViewById(R.id.button);
mListView = a.getListView();
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 5ba6bf9..85a77d6 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -124,8 +124,8 @@
Roboto-Bold.ttf \
Roboto-Italic.ttf \
Roboto-BoldItalic.ttf \
- DroidSansArabic.ttf \
DroidNaskh-Regular.ttf \
+ DroidNaskh-Regular-Shift.ttf \
DroidSansHebrew-Regular.ttf \
DroidSansHebrew-Bold.ttf \
DroidSansThai.ttf \
diff --git a/data/fonts/DroidNaskh-Regular-Shift.ttf b/data/fonts/DroidNaskh-Regular-Shift.ttf
new file mode 100644
index 0000000..0cb843d
--- /dev/null
+++ b/data/fonts/DroidNaskh-Regular-Shift.ttf
Binary files differ
diff --git a/data/fonts/fallback_fonts-ja.xml b/data/fonts/fallback_fonts-ja.xml
index 62491d8..db998d3 100644
--- a/data/fonts/fallback_fonts-ja.xml
+++ b/data/fonts/fallback_fonts-ja.xml
@@ -34,7 +34,7 @@
<familyset>
<family>
<fileset>
- <file>DroidSansArabic.ttf</file>
+ <file>DroidNaskh-Regular-Shift.ttf</file>
</fileset>
</family>
<family>
diff --git a/data/fonts/fallback_fonts.xml b/data/fonts/fallback_fonts.xml
index ba01947..a4b5212 100644
--- a/data/fonts/fallback_fonts.xml
+++ b/data/fonts/fallback_fonts.xml
@@ -34,7 +34,7 @@
<familyset>
<family>
<fileset>
- <file>DroidSansArabic.ttf</file>
+ <file>DroidNaskh-Regular-Shift.ttf</file>
</fileset>
</family>
<family>
diff --git a/data/fonts/fonts.mk b/data/fonts/fonts.mk
index db26765..702b069 100644
--- a/data/fonts/fonts.mk
+++ b/data/fonts/fonts.mk
@@ -24,8 +24,8 @@
Roboto-Bold.ttf \
Roboto-Italic.ttf \
Roboto-BoldItalic.ttf \
- DroidSansArabic.ttf \
DroidNaskh-Regular.ttf \
+ DroidNaskh-Regular-Shift.ttf \
DroidSansHebrew-Regular.ttf \
DroidSansHebrew-Bold.ttf \
DroidSansThai.ttf \
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index 2e18a10..e403205 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -20,3 +20,5 @@
$(call inherit-product, frameworks/base/data/sounds/AudioPackage4.mk)
$(call inherit-product, frameworks/base/data/sounds/AudioPackage5.mk)
$(call inherit-product, frameworks/base/data/sounds/AudioPackage6.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage7.mk)
+$(call inherit-product, frameworks/base/data/sounds/AudioPackage7alt.mk)
diff --git a/docs/html/guide/developing/debugging/ddms.jd b/docs/html/guide/developing/debugging/ddms.jd
index 80b1e47..9892e49 100644
--- a/docs/html/guide/developing/debugging/ddms.jd
+++ b/docs/html/guide/developing/debugging/ddms.jd
@@ -128,9 +128,7 @@
classes and threads are allocating the objects. This allows you to track, in real time, where
objects are being allocated when you perform certain actions in your application. This
information is valuable for assessing memory usage that can affect application performance.
- If you want more granular control over where allocation data is collected, use the
- {@link android.os.Debug#startAllocCounting()} and {@link android.os.Debug#stopAllocCounting()}
- methods.</p>
+ </p>
<p>To track memory allocation of objects:</p>
<ol>
diff --git a/docs/html/images/training/backward-compatible-ui-classes-eclair.png b/docs/html/images/training/backward-compatible-ui-classes-eclair.png
new file mode 100644
index 0000000..febba5b
--- /dev/null
+++ b/docs/html/images/training/backward-compatible-ui-classes-eclair.png
Binary files differ
diff --git a/docs/html/images/training/backward-compatible-ui-classes-honeycomb.png b/docs/html/images/training/backward-compatible-ui-classes-honeycomb.png
new file mode 100644
index 0000000..ba14252
--- /dev/null
+++ b/docs/html/images/training/backward-compatible-ui-classes-honeycomb.png
Binary files differ
diff --git a/docs/html/images/training/backward-compatible-ui-classes.png b/docs/html/images/training/backward-compatible-ui-classes.png
new file mode 100644
index 0000000..c5a3cd8
--- /dev/null
+++ b/docs/html/images/training/backward-compatible-ui-classes.png
Binary files differ
diff --git a/docs/html/images/training/backward-compatible-ui-gb.png b/docs/html/images/training/backward-compatible-ui-gb.png
new file mode 100644
index 0000000..621ee63
--- /dev/null
+++ b/docs/html/images/training/backward-compatible-ui-gb.png
Binary files differ
diff --git a/docs/html/images/training/backward-compatible-ui-ics.png b/docs/html/images/training/backward-compatible-ui-ics.png
new file mode 100644
index 0000000..6460554
--- /dev/null
+++ b/docs/html/images/training/backward-compatible-ui-ics.png
Binary files differ
diff --git a/docs/html/images/training/implementing-navigation-up.png b/docs/html/images/training/implementing-navigation-up.png
new file mode 100644
index 0000000..18f3779
--- /dev/null
+++ b/docs/html/images/training/implementing-navigation-up.png
Binary files differ
diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs
index 5297c23..686bde3 100644
--- a/docs/html/resources/resources_toc.cs
+++ b/docs/html/resources/resources_toc.cs
@@ -227,7 +227,31 @@
</li>
</ul>
</li>
-
+
+ <li class="toggle-list">
+ <div><a href="<?cs var:toroot ?>training/backward-compatible-ui/index.html">
+ <span class="en">Creating Backward-Compatible UIs<span class="new"> new!</span></span>
+ </a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>training/backward-compatible-ui/abstracting.html">
+ <span class="en">Abstracting the New APIs</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/backward-compatible-ui/new-implementation.html">
+ <span class="en">Proxying to the New APIs</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/backward-compatible-ui/older-implementation.html">
+ <span class="en">Creating an Implementation with Older APIs</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/backward-compatible-ui/using-component.html">
+ <span class="en">Using the Version-Aware Component</span>
+ </a>
+ </li>
+ </ul>
+ </li>
+
<li class="toggle-list">
<div><a href="<?cs var:toroot ?>training/enterprise/index.html">
<span class="en">Developing for Enterprise</span>
@@ -278,7 +302,32 @@
</a>
</li>
</ul>
- </li>
+ </li>
+
+ <li class="toggle-list">
+ <div><a href="<?cs var:toroot ?>training/implementing-navigation/index.html">
+ <span class="en">Implementing Effective Navigation<span class="new"> new!</span></span>
+ </a></div>
+ <ul>
+ <li><a href="<?cs var:toroot ?>training/implementing-navigation/lateral.html">
+ <span class="en">Implementing Lateral Navigation</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/implementing-navigation/ancestral.html">
+ <span class="en">Implementing Ancestral Navigation</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/implementing-navigation/temporal.html">
+ <span class="en">Implementing Temporal Navigation</span>
+ </a>
+ </li>
+ <li><a href="<?cs var:toroot ?>training/implementing-navigation/descendant.html">
+ <span class="en">Implementing Descendant Navigation</span>
+ </a>
+ </li>
+ </ul>
+ </li>
+
<li class="toggle-list">
<div><a href="<?cs var:toroot ?>training/tv/index.html">
<span class="en">Designing for TV<span class="new"> new!</span></span>
diff --git a/docs/html/shareables/training/EffectiveNavigation.zip b/docs/html/shareables/training/EffectiveNavigation.zip
new file mode 100644
index 0000000..f21af45
--- /dev/null
+++ b/docs/html/shareables/training/EffectiveNavigation.zip
Binary files differ
diff --git a/docs/html/shareables/training/TabCompat.zip b/docs/html/shareables/training/TabCompat.zip
new file mode 100644
index 0000000..b70b442
--- /dev/null
+++ b/docs/html/shareables/training/TabCompat.zip
Binary files differ
diff --git a/docs/html/training/backward-compatible-ui/abstracting.jd b/docs/html/training/backward-compatible-ui/abstracting.jd
new file mode 100644
index 0000000..1141b54
--- /dev/null
+++ b/docs/html/training/backward-compatible-ui/abstracting.jd
@@ -0,0 +1,111 @@
+page.title=Abstracting the New APIs
+parent.title=Creating Backward-Compatible UIs
+parent.link=index.html
+
+trainingnavtop=true
+next.title=Proxying to the New APIs
+next.link=new-implementation.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ul>
+ <li><a href="#prepare-abstraction">Prepare for Abstraction</a></li>
+ <li><a href="#create-abstract-tab">Create an Abstract Tab Interface</a></li>
+ <li><a href="#abstract-actionbar-tab">Abstract ActionBar.Tab</a></li>
+ <li><a href="#abstract-actionbar-methods">Abstract ActionBar Tab Methods</a></li>
+</ul>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li>
+ <li><a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">Action Bar Tabs</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/TabCompat.zip"
+ class="button">Download the sample app</a>
+<p class="filename">TabCompat.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>Suppose you want to use <a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">action bar tabs</a> as the primary form of top-level navigation in your application. Unfortunately, the {@link android.app.ActionBar} APIs are only available in Android 3.0 or later (API level 11+). Thus, if you want to distribute your application to devices running earlier versions of the platform, you need to provide an implementation that supports the newer API while providing a fallback mechanism that uses older APIs.</p>
+
+<p>In this class, you build a tabbed user interface (UI) component that uses abstract classes with version-specific implementations to provide backward-compatibility. This lesson describes how to create an abstraction layer for the new tab APIs as the first step toward building the tab component.</p>
+
+<h2 id="prepare-abstraction">Prepare for Abstraction</h2>
+
+<p><a href="http://en.wikipedia.org/wiki/Abstraction_(computer_science)">Abstraction</a> in the Java programming language involves the creation of one or more interfaces or abstract classes to hide implementation details. In the case of newer Android APIs, you can use abstraction to build version-aware components that use the current APIs on newer devices, and fallback to older, more compatible APIs on older devices.</p>
+
+<p>When using this approach, you first determine what newer classes you want to be able to use in a backward compatible way, then create abstract classes, based on the public interfaces of the newer classes. In defining the abstraction interfaces, you should mirror the newer API as much as possible. This maximizes forward-compatibility and makes it easier to drop the abstraction layer in the future when it is no longer necessary.</p>
+
+<p>After creating abstract classes for these new APIs, any number of implementations can be created and chosen at runtime. For the purposes of backward-compatibility, these implementations can vary by required API level. Thus, one implementation may use recently released APIs, while others can use older APIs.</p>
+
+<h2 id="create-abstract-tab">Create an Abstract Tab Interface</h2>
+
+<p>In order to create a backward-compatible version of tabs, you should first determine which features and specific APIs your application requires. In the case of top-level section tabs, suppose you have the following functional requirements:</p>
+
+<ol>
+<li>Tab indicators should show text and an icon.</li>
+<li>Tabs can be associated with a fragment instance.</li>
+<li>The activity should be able to listen for tab changes.</li>
+</ol>
+
+<p>Preparing these requirements in advance allows you to control the scope of your abstraction layer. This means that you can spend less time creating multiple implementations of your abstraction layer and begin using your new backward-compatible implementation sooner.</p>
+
+<p>The key APIs for tabs are in {@link android.app.ActionBar} and {@link android.app.ActionBar.Tab ActionBar.Tab}. These are the APIs to abstract in order to make your tabs version-aware. The requirements for this example project call for compatibility back to Eclair (API level 5) while taking advantage of the new tab features in Honeycomb (API Level 11). A diagram of the class structure to support these two implementations and their abstract base classes (or interfaces) is shown below.</p>
+
+<img src="{@docRoot}images/training/backward-compatible-ui-classes.png"
+ alt="Class diagram of abstract base classes and version-specific implementations." id="figure-classes">
+
+<p class="img-caption"><strong>Figure 1.</strong> Class diagram of abstract base classes and version-specific implementations.</p>
+
+<h2 id="abstract-actionbar-tab">Abstract ActionBar.Tab</h2>
+
+<p>Get started on building your tab abstraction layer by creating an abstract class representing a tab, that mirrors the {@link android.app.ActionBar.Tab ActionBar.Tab} interface:</p>
+
+<pre>
+public abstract class CompatTab {
+ ...
+ public abstract CompatTab setText(int resId);
+ public abstract CompatTab setIcon(int resId);
+ public abstract CompatTab setTabListener(
+ CompatTabListener callback);
+ public abstract CompatTab setFragment(Fragment fragment);
+
+ public abstract CharSequence getText();
+ public abstract Drawable getIcon();
+ public abstract CompatTabListener getCallback();
+ public abstract Fragment getFragment();
+ ...
+}
+</pre>
+
+<p>You can use an abstract class instead of an interface here to simplify the implementation of common features such as association of tab objects with activities (not shown in the code snippet).</p>
+
+<h2 id="abstract-actionbar-methods">Abstract ActionBar Tab Methods</h2>
+
+<p>Next, define an abstract class that allows you to create and add tabs to an activity, like {@link android.app.ActionBar#newTab ActionBar.newTab()} and {@link android.app.ActionBar#addTab ActionBar.addTab()}:</p>
+
+<pre>
+public abstract class TabHelper {
+ ...
+
+ public CompatTab newTab(String tag) {
+ // This method is implemented in a later lesson.
+ }
+
+ public abstract void addTab(CompatTab tab);
+
+ ...
+}
+</pre>
+
+<p>In the next lessons, you create implementations for <code>TabHelper</code> and <code>CompatTab</code> that work across both older and newer platform versions.</p>
diff --git a/docs/html/training/backward-compatible-ui/index.jd b/docs/html/training/backward-compatible-ui/index.jd
new file mode 100644
index 0000000..7e27e68
--- /dev/null
+++ b/docs/html/training/backward-compatible-ui/index.jd
@@ -0,0 +1,57 @@
+page.title=Creating Backward-Compatible UIs
+
+trainingnavtop=true
+startpage=true
+next.title=Abstracting the New Implementation
+next.link=abstracting.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+
+<ul>
+ <li>API level 5</li>
+ <li><a href="{@docRoot}sdk/compatibility-library.html">The Android Support Package</a></li>
+</ul>
+
+<h2>You should also read</h2>
+
+<ul>
+ <li><a href="{@docRoot}resources/samples/ActionBarCompat/index.html">ActionBarCompat</a></li>
+ <li><a href="http://android-developers.blogspot.com/2010/07/how-to-have-your-cupcake-and-eat-it-too.html">How to have your (Cup)cake and eat it too</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/TabCompat.zip"
+ class="button">Download the sample app</a>
+<p class="filename">TabCompat.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>This class demonstrates how to use UI components and APIs available in newer versions of Android in a backward-compatible way, ensuring that your application still runs on previous versions of the platform.</p>
+
+<p>Throughout this class, the new <a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">Action Bar Tabs</a> feature introduced in Android 3.0 (API level 11) serves as the guiding example, but you can apply these techniques to other UI components and API features.</p>
+
+<h2 id="lessons">Lessons</h2>
+
+
+<dl>
+ <dt><strong><a href="abstracting.html">Abstracting the New APIs</a></strong></dt>
+ <dd>Determine which features and APIs your application needs. Learn how to define application-specific, intermediary Java interfaces that abstract the implementation of the UI component to your application.</dd>
+
+ <dt><strong><a href="new-implementation.html">Proxying to the New APIs</a></strong></dt>
+ <dd>Learn how to create an implementation of your interface that uses newer APIs.</dd>
+
+ <dt><strong><a href="older-implementation.html">Creating an Implementation with Older APIs</a></strong></dt>
+ <dd>Learn how to create a custom implementation of your interface that uses older APIs.</dd>
+
+ <dt><strong><a href="using-component.html">Using the Version-Aware Component</a></strong></dt>
+ <dd>Learn how to choose an implementation to use at runtime, and begin using the interface in your application.</dd>
+</dl>
diff --git a/docs/html/training/backward-compatible-ui/new-implementation.jd b/docs/html/training/backward-compatible-ui/new-implementation.jd
new file mode 100644
index 0000000..5b8b51c
--- /dev/null
+++ b/docs/html/training/backward-compatible-ui/new-implementation.jd
@@ -0,0 +1,113 @@
+page.title=Proxying to the New APIs
+parent.title=Creating Backward-Compatible UIs
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Abstracting the New APIs
+previous.link=abstracting.html
+next.title=Creating an Implementation with Older APIs
+next.link=older-implementation.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#new-tabs">Implement Tabs Using New APIs</a></li>
+ <li><a href="#compattabhoneycomb">Implement CompatTabHoneycomb</a></li>
+ <li><a href="#tabhelperhoneycomb">Implement TabHelperHoneycomb</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li>
+ <li><a href="{@docRoot}guide/topics/ui/actionbar.html#Tabs">Action Bar Tabs</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/TabCompat.zip"
+ class="button">Download the sample app</a>
+<p class="filename">TabCompat.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>This lesson shows you how to subclass the <code>CompatTab</code> and <code>TabHelper</code> abstract classes and use new APIs. Your application can use this implementation on devices running a platform version that supports them.</p>
+
+<h2 id="new-tabs">Implement Tabs Using New APIs</h2>
+
+<p>The concrete classes for <code>CompatTab</code> and <code>TabHelper</code> that use newer APIs are a <em>proxy</em> implementation. Since the abstract classes defined in the previous lesson mirror the new APIs (class structure, method signatures, etc.), the concrete classes that use these newer APIs simply proxy method calls and their results.</p>
+
+<p>You can directly use newer APIs in these concrete classes—and not crash on earlier devices—because of lazy class loading. Classes are loaded and initialized on first access—instantiating the class or accessing one of its static fields or methods for the first time. Thus, as long as you don't instantiate the Honeycomb-specific implementations on pre-Honeycomb devices, the Dalvik VM won't throw any {@link java.lang.VerifyError} exceptions.</p>
+
+<p>A good naming convention for this implementation is to append the API level or platform version code name corresponding to the APIs required by the concrete classes. For example, the native tab implementation can be provided by <code>CompatTabHoneycomb</code> and <code>TabHelperHoneycomb</code> classes, since they rely on APIs available in Android 3.0 (API level 11) or later.</p>
+
+<img src="{@docRoot}images/training/backward-compatible-ui-classes-honeycomb.png"
+ alt="Class diagram for the Honeycomb implementation of tabs." id="figure-classes">
+
+<p class="img-caption"><strong>Figure 1.</strong> Class diagram for the Honeycomb implementation of tabs.</p>
+
+<h2 id="compattabhoneycomb">Implement CompatTabHoneycomb</h2>
+
+<p><code>CompatTabHoneycomb</code> is the implementation of the <code>CompatTab</code> abstract class that <code>TabHelperHoneycomb</code> uses to reference individual tabs. <code>CompatTabHoneycomb</code> simply proxies all method calls to its contained {@link android.app.ActionBar.Tab} object.</p>
+
+<p>Begin implementing <code>CompatTabHoneycomb</code> using the new {@link android.app.ActionBar.Tab ActionBar.Tab} APIs:</p>
+
+<pre>
+public class CompatTabHoneycomb extends CompatTab {
+ // The native tab object that this CompatTab acts as a proxy for.
+ ActionBar.Tab mTab;
+ ...
+
+ protected CompatTabHoneycomb(FragmentActivity activity, String tag) {
+ ...
+ // Proxy to new ActionBar.newTab API
+ mTab = activity.getActionBar().newTab();
+ }
+
+ public CompatTab setText(int resId) {
+ // Proxy to new ActionBar.Tab.setText API
+ mTab.setText(resId);
+ return this;
+ }
+
+ ...
+ // Do the same for other properties (icon, callback, etc.)
+}
+</pre>
+
+<h2 id="tabhelperhoneycomb">Implement TabHelperHoneycomb</h2>
+
+<p><code>TabHelperHoneycomb</code> is the implementation of the <code>TabHelper</code> abstract class that proxies method calls to an actual {@link android.app.ActionBar}, obtained from its contained {@link android.app.Activity}.</p>
+
+<p>Implement <code>TabHelperHoneycomb</code>, proxying method calls to the {@link android.app.ActionBar} API:</p>
+
+<pre>
+public class TabHelperHoneycomb extends TabHelper {
+ ActionBar mActionBar;
+ ...
+
+ protected void setUp() {
+ if (mActionBar == null) {
+ mActionBar = mActivity.getActionBar();
+ mActionBar.setNavigationMode(
+ ActionBar.NAVIGATION_MODE_TABS);
+ }
+ }
+
+ public void addTab(CompatTab tab) {
+ ...
+ // Tab is a CompatTabHoneycomb instance, so its
+ // native tab object is an ActionBar.Tab.
+ mActionBar.addTab((ActionBar.Tab) tab.getTab());
+ }
+
+ // The other important method, newTab() is part of
+ // the base implementation.
+}
+</pre>
\ No newline at end of file
diff --git a/docs/html/training/backward-compatible-ui/older-implementation.jd b/docs/html/training/backward-compatible-ui/older-implementation.jd
new file mode 100644
index 0000000..5006123
--- /dev/null
+++ b/docs/html/training/backward-compatible-ui/older-implementation.jd
@@ -0,0 +1,126 @@
+page.title=Creating an Implementation with Older APIs
+parent.title=Creating Backward-Compatible UIs
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Proxying to the New APIs
+previous.link=new-implementation.html
+next.title=Using the Version-Aware Component
+next.link=using-component.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#decide-substitute">Decide on a Substitute Solution</a></li>
+ <li><a href="#older-tabs">Implement Tabs Using Older APIs</a></li>
+</ol>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/TabCompat.zip"
+ class="button">Download the sample app</a>
+<p class="filename">TabCompat.zip</p>
+</div>
+
+</div>
+</div>
+
+
+<p>This lesson discusses how to create an implementation that mirrors newer APIs yet supports older devices.</p>
+
+<h2 id="decide-substitute">Decide on a Substitute Solution</h2>
+
+<p>The most challenging task in using newer UI features in a backward-compatible way is deciding on and implementing an older (fallback) solution for older platform versions. In many cases, it's possible to fulfill the purpose of these newer UI components using older UI framework features. For example:</p>
+
+<ul>
+
+<li>
+<p>Action bars can be implemented using a horizontal {@link android.widget.LinearLayout} containing image buttons, either as custom title bars or as views in your activity layout. Overflow actions can be presented under the device <em>Menu</em> button.</p>
+</li>
+
+<li>
+<p>Action bar tabs can be implemented using a horizontal {@link android.widget.LinearLayout} containing buttons, or using the {@link android.widget.TabWidget} UI element.</p>
+</li>
+
+<li>
+<p>{@link android.widget.NumberPicker} and {@link android.widget.Switch} widgets can be implemented using {@link android.widget.Spinner} and {@link android.widget.ToggleButton} widgets, respectively.</p>
+</li>
+
+<li>
+<p>{@link android.widget.ListPopupWindow} and {@link android.widget.PopupMenu} widgets can be implemented using {@link android.widget.PopupWindow} widgets.</p>
+</li>
+
+</ul>
+
+<p>There generally isn't a one-size-fits-all solution for backporting newer UI components to older devices. Be mindful of the user experience: on older devices, users may not be familiar with newer design patterns and UI components. Give some thought as to how the same functionality can be delivered using familiar elements. In many cases this is less of a concern—if newer UI components are prominent in the application ecosystem (such as the action bar), or where the interaction model is extremely simple and intuitive (such as swipe views using a {@link android.support.v4.view.ViewPager}).</p>
+
+<h2 id="older-tabs">Implement Tabs Using Older APIs</h2>
+
+<p>To create an older implementation of action bar tabs, you can use a {@link android.widget.TabWidget} and {@link android.widget.TabHost} (although one can alternatively use horizontally laid-out {@link android.widget.Button} widgets). Implement this in classes called <code>TabHelperEclair</code> and <code>CompatTabEclair</code>, since this implementation uses APIs introduced no later than Android 2.0 (Eclair).</p>
+
+
+<img src="{@docRoot}images/training/backward-compatible-ui-classes-eclair.png"
+ alt="Class diagram for the Eclair implementation of tabs." id="figure-classes">
+
+<p class="img-caption"><strong>Figure 1.</strong> Class diagram for the Eclair implementation of tabs.</p>
+
+<p>The <code>CompatTabEclair</code> implementation stores tab properties such as the tab text and icon in instance variables, since there isn't an {@link android.app.ActionBar.Tab ActionBar.Tab} object available to handle this storage:</p>
+
+<pre>
+public class CompatTabEclair extends CompatTab {
+ // Store these properties in the instance,
+ // as there is no ActionBar.Tab object.
+ private CharSequence mText;
+ ...
+
+ public CompatTab setText(int resId) {
+ // Our older implementation simply stores this
+ // information in the object instance.
+ mText = mActivity.getResources().getText(resId);
+ return this;
+ }
+
+ ...
+ // Do the same for other properties (icon, callback, etc.)
+}
+</pre>
+
+<p>The <code>TabHelperEclair</code> implementation makes use of methods on the
+{@link android.widget.TabHost} widget for creating {@link android.widget.TabHost.TabSpec}
+objects and tab indicators:</p>
+
+<pre>
+public class TabHelperEclair extends TabHelper {
+ private TabHost mTabHost;
+ ...
+
+ protected void setUp() {
+ if (mTabHost == null) {
+ // Our activity layout for pre-Honeycomb devices
+ // must contain a TabHost.
+ mTabHost = (TabHost) mActivity.findViewById(
+ android.R.id.tabhost);
+ mTabHost.setup();
+ }
+ }
+
+ public void addTab(CompatTab tab) {
+ ...
+ TabSpec spec = mTabHost
+ .newTabSpec(tag)
+ .setIndicator(tab.getText()); // And optional icon
+ ...
+ mTabHost.addTab(spec);
+ }
+
+ // The other important method, newTab() is part of
+ // the base implementation.
+}
+</pre>
+
+<p>You now have two implementations of <code>CompatTab</code> and <code>TabHelper</code>: one that works on devices running Android 3.0 or later and uses new APIs, and another that works on devices running Android 2.0 or later and uses older APIs. The next lesson discusses using these implementations in your application.</p>
diff --git a/docs/html/training/backward-compatible-ui/using-component.jd b/docs/html/training/backward-compatible-ui/using-component.jd
new file mode 100644
index 0000000..4bf7fa0
--- /dev/null
+++ b/docs/html/training/backward-compatible-ui/using-component.jd
@@ -0,0 +1,143 @@
+page.title=Using the Version-Aware Component
+parent.title=Creating Backward-Compatible UIs
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Creating an Implementation with Older APIs
+previous.link=older-implementation.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#switching-logic">Add the Switching Logic</a></li>
+ <li><a href="#layout">Create a Version-Aware Activity Layout</a></li>
+ <li><a href="#use-tabhelper">Use TabHelper in Your Activity</a></li>
+</ol>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/TabCompat.zip"
+ class="button">Download the sample app</a>
+<p class="filename">TabCompat.zip</p>
+</div>
+
+</div>
+</div>
+
+<p>Now that you have two implementations of <code>TabHelper</code> and <code>CompatTab</code>—one for Android 3.0 and later and one for earlier versions of the platform—it's time to do something with these implementations. This lesson discusses creating the logic for switching between these implementations, creating version-aware layouts, and finally using the backward-compatible UI component.</p>
+
+<h2 id="switching-logic">Add the Switching Logic</h2>
+
+<p>The <code>TabHelper</code> abstract class acts as a <a href="http://en.wikipedia.org/wiki/Factory_(software_concept)">factory</a> for creating version-appropriate <code>TabHelper</code> and <code>CompatTab</code> instances, based on the current device's platform version:</p>
+
+<pre>
+public abstract class TabHelper {
+ ...
+ // Usage is TabHelper.createInstance(activity)
+ public static TabHelper createInstance(FragmentActivity activity) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return new TabHelperHoneycomb(activity);
+ } else {
+ return new TabHelperEclair(activity);
+ }
+ }
+
+ // Usage is mTabHelper.newTab("tag")
+ public CompatTab newTab(String tag) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ return new CompatTabHoneycomb(mActivity, tag);
+ } else {
+ return new CompatTabEclair(mActivity, tag);
+ }
+ }
+ ...
+}
+</pre>
+
+<h2 id="layout">Create a Version-Aware Activity Layout</h2>
+
+<p>The next step is to provide layouts for your activity that can support the two tab implementations. For the older implementation (<code>TabHelperEclair</code>), you need to ensure that your activity layout contains a {@link android.widget.TabWidget} and {@link android.widget.TabHost}, along with a container for tab contents:</p>
+
+<p><strong>res/layout/main.xml:</strong></p>
+
+<pre>
+<!-- This layout is for API level 5-10 only. -->
+<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="5dp">
+
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1" />
+
+ </LinearLayout>
+</TabHost>
+</pre>
+
+<p>For the <code>TabHelperHoneycomb</code> implementation, all you need is a {@link android.widget.FrameLayout} to contain the tab contents, since the tab indicators are provided by the {@link android.app.ActionBar}:</p>
+
+<p><strong>res/layout-v11/main.xml:</strong></p>
+
+<pre>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+</pre>
+
+<p>At runtime, Android will decide which version of the <code>main.xml</code> layout to inflate depending on the platform version. This is the same logic shown in the previous section to determine which <code>TabHelper</code> implementation to use.</p>
+
+<h2 id="use-tabhelper">Use TabHelper in Your Activity</h2>
+
+<p>In your activity's {@link android.app.Activity#onCreate onCreate()} method, you can obtain a <code>TabHelper</code> object and add tabs with the following code:</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.main);
+
+ TabHelper tabHelper = TabHelper.createInstance(this);
+ tabHelper.setUp();
+
+ CompatTab photosTab = tabHelper
+ .newTab("photos")
+ .setText(R.string.tab_photos);
+ tabHelper.addTab(photosTab);
+
+ CompatTab videosTab = tabHelper
+ .newTab("videos")
+ .setText(R.string.tab_videos);
+ tabHelper.addTab(videosTab);
+}
+</pre>
+
+<p>When running the application, this code inflates the correct activity layout and instantiates either a <code>TabHelperHoneycomb</code> or <code>TabHelperEclair</code> object. The concrete class that's actually used is opaque to the activity, since they share the common <code>TabHelper</code> interface.</p>
+
+<p>Below are two screenshots of this implementation running on an Android 2.3 and Android 4.0 device.</p>
+
+<img src="{@docRoot}images/training/backward-compatible-ui-gb.png"
+ alt="Example screenshot of tabs running on an Android 2.3 device (using TabHelperEclair)." width="200">
+
+<img src="{@docRoot}images/training/backward-compatible-ui-ics.png"
+ alt="Example screenshots of tabs running on an Android 4.0 device (using TabHelperHoneycomb)." width="200">
+
+<p class="img-caption"><strong>Figure 1.</strong> Example screenshots of backward-compatible tabs running on an Android 2.3 device (using <code>TabHelperEclair</code>) and an Android 4.0 device (using <code>TabHelperHoneycomb</code>).</p>
diff --git a/docs/html/training/implementing-navigation/ancestral.jd b/docs/html/training/implementing-navigation/ancestral.jd
new file mode 100644
index 0000000..495b45d
--- /dev/null
+++ b/docs/html/training/implementing-navigation/ancestral.jd
@@ -0,0 +1,124 @@
+page.title=Implementing Ancestral Navigation
+parent.title=Implementing Effective Navigation
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Implementing Lateral Navigation
+previous.link=lateral.html
+next.title=Implementing Temporal Navigation
+next.link=temporal.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#up">Implement <em>Up</em> Navigation</a></li>
+ <li><a href="#app-home">Properly Handle the Application Home Screen</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}training/design-navigation/ancestral-temporal.html">Providing Ancestral and Temporal Navigation</a></li>
+ <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a></li>
+ <li><a href="{@docRoot}design/patterns/navigation.html">Android Design: Navigation</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/EffectiveNavigation.zip"
+ class="button">Download the sample app</a>
+<p class="filename">EffectiveNavigation.zip</p>
+</div>
+
+</div>
+</div>
+
+
+<p><em>Ancestral navigation</em> is up the application's information hierarchy, where the top of the hierarchy (or root) is the application's home screen. This navigation concept is described in <a href="{@docRoot}training/design-navigation/ancestral-temporal.html">Designing Effective Navigation</a>. This lesson discusses how to provide ancestral navigation using the <em>Up</em> button in the action bar.</p>
+
+
+<h2 id="up">Implement <em>Up</em> Navigation</h2>
+
+<p>When implementing ancestral navigation, all screens in your application that aren't the home screen should offer a means of navigating to the immediate parent screen in the hierarchy via the <em>Up</em> button in the action bar.</p>
+
+
+<img src="{@docRoot}images/training/implementing-navigation-up.png"
+ alt="The Up button in the action bar." id="figure-up">
+
+<p class="img-caption"><strong>Figure 1.</strong> The <em>Up</em> button in the action bar.</p>
+
+<p>Regardless of how the current screen was reached, pressing this button should always take the user to the same screen in the hierarchy.</p>
+
+<p>To implement <em>Up</em>, enable it in the action bar in your activity's {@link android.app.Activity#onCreate onCreate()} method:</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+ ...
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ ...
+}
+</pre>
+
+<p>You should also handle <code>android.R.id.home</code> in {@link android.app.Activity#onOptionsItemSelected onOptionsItemSelected()}. This resource is the menu item ID for the <em>Home</em> (or <em>Up</em>) button. To ensure that a specific parent activity is shown, <em>DO NOT</em> simply call {@link android.app.Activity#finish finish()}. Instead, use an intent such as the one described below.</p>
+
+<pre>
+{@literal @}Override
+public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // This is called when the Home (Up) button is pressed
+ // in the Action Bar.
+ Intent parentActivityIntent = new Intent(this, MyParentActivity.class);
+ parentActivityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_TOP |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(parentActivityIntent);
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+}
+</pre>
+
+<p>When the current activity belongs to a task from a different application—for example if it was reached via an intent from another application—pressing <em>Up</em> should create a new task for the application with a synthesized back stack. This approach is described in <a href="{@docRoot}design/patterns/navigation.html">Android Design: Navigation</a> and the {@link android.support.v4.app.TaskStackBuilder} class reference.</p>
+
+<p>The {@link android.support.v4.app.NavUtils} and {@link android.support.v4.app.TaskStackBuilder} classes in the <a href="{@docRoot}sdk/compatibility-library.html">Android Support Package</a> provide helpers for implementing this behavior correctly. An example usage of these two helper classes is below:</p>
+
+<pre>
+{@literal @}Override
+public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent upIntent = new Intent(this, MyParentActivity.class);
+ if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
+ // This activity is not part of the application's task, so create a new task
+ // with a synthesized back stack.
+ TaskStackBuilder.from(this)
+ .addNextIntent(new Intent(this, MyGreatGrandParentActivity.class))
+ .addNextIntent(new Intent(this, MyGrandParentActivity.class))
+ .addNextIntent(upIntent)
+ .startActivities();
+ finish();
+ } else {
+ // This activity is part of the application's task, so simply
+ // navigate up to the hierarchical parent activity.
+ NavUtils.navigateUpTo(this, upIntent);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+}
+</pre>
+
+<h2 id="app-home">Properly Handle the Application Home Screen</h2>
+
+<p>By default, the <em>Home</em> button in the action bar is interactive. Since it does not make much sense to navigate home—or up one level—while on the home screen, you should disable the button like so:</p>
+
+<pre>
+getActionBar().setHomeButtonEnabled(false);
+</pre>
diff --git a/docs/html/training/implementing-navigation/descendant.jd b/docs/html/training/implementing-navigation/descendant.jd
new file mode 100644
index 0000000..7d0063c
--- /dev/null
+++ b/docs/html/training/implementing-navigation/descendant.jd
@@ -0,0 +1,65 @@
+page.title=Implementing Descendant Navigation
+parent.title=Implementing Effective Navigation
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Implementing Temporal Navigation
+previous.link=temporal.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#master-detail">Implement Master/Detail Flows Across Handsets and Tablets</a></li>
+ <li><a href="#external-activities">Navigate into External Activities</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}training/design-navigation/descendant-lateral.html">Providing Descendant and Lateral Navigation</a></li>
+ <li><a href="{@docRoot}design/patterns/app-structure.html">Android Design: App Structure</a></li>
+ <li><a href="{@docRoot}design/patterns/multi-pane-layouts.html">Android Design: Multi-pane Layouts</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/EffectiveNavigation.zip"
+ class="button">Download the sample app</a>
+<p class="filename">EffectiveNavigation.zip</p>
+</div>
+
+</div>
+</div>
+
+
+<p><em>Descendant navigation</em> is navigation down the application's information hierarchy. This is described in <a href="{@docRoot}training/design-navigation/descendant-lateral.html">Designing Effective Navigation</a> and also covered in <a href="{@docRoot}design/patterns/app-structure.html">Android Design: Application Structure</a>.</p>
+
+<p>Descendant navigation is usually implemented using {@link android.content.Intent} objects and {@link android.content.Context#startActivity startActivity()}, or by adding fragments to an activity using {@link android.app.FragmentTransaction} objects. This lesson covers other interesting cases that arise when implementing descendant navigation.</p>
+
+<h2 id="master-detail">Implement Master/Detail Flows Across Handsets and Tablets</h2>
+
+<p>In a <em>master/detail</em> navigation flow, a <em>master</em> screen contains a list of items in a collection, and a <em>detail</em> screen shows detailed information about a specific item within that collection. Implementing navigation from the master screen to the detail screen is one form of descendant navigation.</p>
+
+<p>Handset touchscreens are most suitable for displaying one screen at a time (either the master or the detail screen); this concern is further discussed in <a href="{@docRoot}training/design-navigation/multiple-sizes.html">Planning for Multiple Touchscreen Sizes</a>. Descendant navigation in this case is often implemented using an {@link android.content.Intent} that starts an activity representing the detail screen. On the other hand, tablet displays, especially when viewed in the landscape orientation, are best suited for showing multiple content panes at a time: the master on the left, and the detail to the right). Here, descendant navigation is usually implemented using a {@link android.app.FragmentTransaction} that adds, removes, or replaces the detail pane with new content.</p>
+
+<p>The basics of implementing this pattern are described in the <a href="{@docRoot}training/multiscreen/adaptui.html">Implementing Adaptive UI Flows</a> lesson of the <em>Designing for Multiple Screens</em> class. The class describes how to implement a master/detail flow using two activities on a handset and a single activity on a tablet.</p>
+
+<h2 id="external-activities">Navigate into External Activities</h2>
+
+<p>There are cases where descending into your application's information hierarchy leads to activities from other applications. For example, when viewing the contact details screen for an entry in the phone address book, a child screen detailing recent posts by the contact on a social network may belong to a social networking application.</p>
+
+<p>When launching another application's activity to allow the user to say, compose an email or pick a photo attachment, you generally don't want the user to return to this activity if they relaunch your application from the Launcher (the device home screen). It would be confusing if touching your application icon brought the user to a "compose email" screen.</p>
+
+<p>To prevent this from occurring, simply add the {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET} flag to the intent used to launch the external activity, like so:</p>
+
+<pre>
+Intent externalActivityIntent = new Intent(Intent.ACTION_PICK);
+externalActivityIntent.setType("image/*");
+externalActivityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+startActivity(externalActivityIntent);
+</pre>
diff --git a/docs/html/training/implementing-navigation/index.jd b/docs/html/training/implementing-navigation/index.jd
new file mode 100644
index 0000000..da61c81
--- /dev/null
+++ b/docs/html/training/implementing-navigation/index.jd
@@ -0,0 +1,68 @@
+page.title=Implementing Effective Navigation
+
+trainingnavtop=true
+startpage=true
+next.title=Implementing Lateral Navigation
+next.link=lateral.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>Dependencies and prerequisites</h2>
+
+<ul>
+ <li>API level 14</li>
+ <li>Understanding of fragments and Android layouts</li>
+ <li><a href="{@docRoot}sdk/compatibility-library.html">The Android Support Package</a></li>
+ <li><a href="{@docRoot}training/design-navigation/index.html">Designing Effective Navigation</a></li>
+</ul>
+
+<h2>You should also read</h2>
+
+<ul>
+ <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Action Bar</a></li>
+ <li><a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a></li>
+ <li><a href="{@docRoot}training/multiscreen/index.html">Designing for Multiple Screens</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/EffectiveNavigation.zip"
+ class="button">Download the sample app</a>
+<p class="filename">EffectiveNavigation.zip</p>
+</div>
+
+</div>
+</div>
+
+
+<p>This class demonstrates how to implement the key navigation design patterns detailed in the
+<a href="{@docRoot}training/design-navigation/index.html">Designing Effective Navigation</a> class.
+The lessons in this class cover implementing navigation up, down, and across your application's <a
+href="{@docRoot}training/design-navigation/screen-planning.html#diagram- relationships">screen
+map</a>.</p>
+
+<p>After reading through the lessons in this class and exploring the associated sample application
+(see right), you should also have a basic understanding of how to use
+{@link android.app.ActionBar} and {@link android.support.v4.view.ViewPager}, two components that are fundamental to core app navigation.</p>
+
+
+<h2 id="lessons">Lessons</h2>
+
+
+<dl>
+ <dt><strong><a href="lateral.html">Implementing Lateral Navigation</a></strong></dt>
+ <dd>Learn how to implement tabs and horizontal paging (swipe views).</dd>
+
+ <dt><strong><a href="ancestral.html">Implementing Ancestral Navigation</a></strong></dt>
+ <dd>Learn how to implement <em>Up</em> navigation.</dd>
+
+ <dt><strong><a href="temporal.html">Implementing Temporal Navigation</a></strong></dt>
+ <dd>Learn how to correctly handle the <em>Back</em> button.</dd>
+
+ <dt><strong><a href="descendant.html">Implementing Descendant Navigation</a></strong></dt>
+ <dd>Learn the finer points of implementing navigation into your application's information hierarchy.</dd>
+</dl>
diff --git a/docs/html/training/implementing-navigation/lateral.jd b/docs/html/training/implementing-navigation/lateral.jd
new file mode 100644
index 0000000..d9ba5c9
--- /dev/null
+++ b/docs/html/training/implementing-navigation/lateral.jd
@@ -0,0 +1,252 @@
+page.title=Implementing Lateral Navigation
+parent.title=Implementing Effective Navigation
+parent.link=index.html
+
+trainingnavtop=true
+next.title=Implementing Ancestral Navigation
+next.link=ancestral.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to</h2>
+<ol>
+ <li><a href="#tabs">Implement Tabs</a></li>
+ <li><a href="#horizontal-paging">Implement Horizontal Paging (Swipe Views)</a></li>
+ <li><a href="#swipe-tabs">Implement Swiping Between Tabs</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}training/design-navigation/descendant-lateral.html">Providing Descendant and Lateral Navigation</a></li>
+ <li><a href="{@docRoot}design/building-blocks/tabs.html">Android Design: Tabs</a></li>
+ <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li>
+</ul>
+
+<h2>Try it out</h2>
+
+<div class="download-box">
+<a href="http://developer.android.com/shareables/training/EffectiveNavigation.zip"
+ class="button">Download the sample app</a>
+<p class="filename">EffectiveNavigation.zip</p>
+</div>
+
+</div>
+</div>
+
+
+<p><em>Lateral navigation</em> is navigation between sibling screens in the application's screen hierarchy (sometimes referred to as a screen map). The most prominent lateral navigation patterns are tabs and horizontal paging (also known as swipe views). This pattern and others are described in <a href="{@docRoot}training/design-navigation/descendant-lateral.html">Designing Effective Navigation</a>. This lesson covers how to implement several of the primary lateral navigation patterns in Android.</p>
+
+<h2 id="tabs">Implement Tabs</h2>
+
+<p>Tabs allow the user to navigate between sibling screens by selecting the appropriate tab indicator available at the top of the display. In Android 3.0 and later, tabs are implemented using the {@link android.app.ActionBar} class, and are generally set up in {@link android.app.Activity#onCreate Activity.onCreate()}. In some cases, such as when horizontal space is limited and/or the number of tabs is large, an appropriate alternate presentation for tabs is a dropdown list (sometimes implemented using a {@link android.widget.Spinner}).</p>
+
+<p>In previous versions of Android, tabs could be implemented using a {@link android.widget.TabWidget} and {@link android.widget.TabHost}. For details, see the <a href="{@docRoot}resources/tutorials/views/hello-tabwidget.html">Hello, Views</a> tutorial.</p>
+
+<p>As of Android 3.0, however, you should use either {@link android.app.ActionBar#NAVIGATION_MODE_TABS} or {@link android.app.ActionBar#NAVIGATION_MODE_LIST} along with the {@link android.app.ActionBar} class.</p>
+
+<h3>Implement the Tabs Pattern with NAVIGATION_MODE_TABS</h3>
+
+<p>To create tabs, you can use the following code in your activity's {@link android.app.Activity#onCreate onCreate()} method. Note that the exact presentation of tabs may vary per device and by the current device configuration, to make best use of available screen space. For example, Android may automatically collapse tabs into a dropdown list if tabs don't fit horizontally in the action bar.</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+ ...
+ final ActionBar actionBar = getActionBar();
+
+ // Specify that tabs should be displayed in the action bar.
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ // Create a tab listener that is called when the user changes tabs.
+ ActionBar.TabListener tabListener = new ActionBar.TabListener() {
+ public void onTabSelected(ActionBar.Tab tab,
+ FragmentTransaction ft) { }
+
+ public void onTabUnselected(ActionBar.Tab tab,
+ FragmentTransaction ft) { }
+
+ public void onTabReselected(ActionBar.Tab tab,
+ FragmentTransaction ft) { }
+ };
+
+ // Add 3 tabs.
+ for (int i = 0; i < 3; i++) {
+ actionBar.addTab(
+ actionBar.newTab()
+ .setText("Tab " + (i + 1))
+ .setTabListener(tabListener));
+ }
+ ...
+}
+</pre>
+
+<h3>Implement the Tabs Pattern with NAVIGATION_MODE_LIST</h3>
+
+<p>To use a dropdown list instead, use the following code in your activity's {@link android.app.Activity#onCreate onCreate()} method. Dropdown lists are often preferable in cases where more information must be shown per navigation item, such as unread message counts, or where the number of available navigation items is large.</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+ ...
+ final ActionBar actionBar = getActionBar();
+
+ // Specify that a dropdown list should be displayed in the action bar.
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+
+ actionBar.setListNavigationCallbacks(
+ // Specify a SpinnerAdapter to populate the dropdown list.
+ new ArrayAdapter<String>(
+ actionBar.getThemedContext(),
+ android.R.layout.simple_list_item_1,
+ android.R.id.text1,
+ new String[]{ "Tab 1", "Tab 2", "Tab 3" }),
+
+ // Provide a listener to be called when an item is selected.
+ new ActionBar.OnNavigationListener() {
+ public boolean onNavigationItemSelected(
+ int position, long id) {
+ // Take action here, e.g. switching to the
+ // corresponding fragment.
+ return true;
+ }
+ });
+ ...
+}
+</pre>
+
+<h2 id="horizontal-paging">Implement Horizontal Paging (Swipe Views)</h2>
+
+<p>Horizontal paging, or swipe views, allow users to <a href="{@docRoot}design/patterns/swipe-views">swipe</a> horizontally on the current screen to navigate to adjacent screens. This pattern can be implemented using the {@link android.support.v4.view.ViewPager} widget, currently available as part of the <a href="{@docRoot}sdk/compatibility-library.html">Android Support Package</a>. For navigating between sibling screens representing a fixed number of sections, it's best to provide the {@link android.support.v4.view.ViewPager} with a {@link android.support.v4.app.FragmentPagerAdapter}. For horizontal paging across collections of objects, it's best to use a {@link android.support.v4.app.FragmentStatePagerAdapter}, which destroys fragments as the user navigates to other pages, minimizing memory usage.</p>
+
+<p>Below is an example of using a {@link android.support.v4.view.ViewPager} to swipe across a collection of objects.</p>
+
+<pre>
+public class CollectionDemoActivity extends FragmentActivity {
+ // When requested, this adapter returns a DemoObjectFragment,
+ // representing an object in the collection.
+ DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;
+ ViewPager mViewPager;
+
+ public void onCreate(Bundle savedInstanceState) {
+ // ViewPager and its adapters use support library
+ // fragments, so use getSupportFragmentManager.
+ mDemoCollectionPagerAdapter =
+ new DemoCollectionPagerAdapter(
+ getSupportFragmentManager());
+ mViewPager = (ViewPager) findViewById(R.id.pager);
+ mViewPager.setAdapter(mDemoCollectionPagerAdapter);
+ }
+}
+
+// Since this is an object collection, use a FragmentStatePagerAdapter,
+// and NOT a FragmentPagerAdapter.
+public class DemoCollectionPagerAdapter extends
+ FragmentStatePagerAdapter {
+ public DemoCollectionPagerAdapter(FragmentManager fm) {
+ super(fm);
+ }
+
+ {@literal @}Override
+ public Fragment getItem(int i) {
+ Fragment fragment = new DemoObjectFragment();
+ Bundle args = new Bundle();
+ // Our object is just an integer :-P
+ args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ {@literal @}Override
+ public int getCount() {
+ return 100;
+ }
+
+ {@literal @}Override
+ public CharSequence getPageTitle(int position) {
+ return "OBJECT " + (position + 1);
+ }
+}
+
+// Instances of this class are fragments representing a single
+// object in our collection.
+public static class DemoObjectFragment extends Fragment {
+ public static final String ARG_OBJECT = "object";
+
+ {@literal @}Override
+ public View onCreateView(LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ // The last two arguments ensure LayoutParams are inflated
+ // properly.
+ View rootView = inflater.inflate(
+ R.layout.fragment_collection_object, container, false);
+ Bundle args = getArguments();
+ ((TextView) rootView.findViewById(android.R.id.text1)).setText(
+ Integer.toString(args.getInt(ARG_OBJECT)));
+ return rootView;
+ }
+}
+</pre>
+
+<p>You can also add indicators to your horizontal paging UI by adding a {@link android.support.v4.view.PagerTitleStrip}. Below is an example layout XML file for an activity whose entire contents are a {@link android.support.v4.view.ViewPager} and a top-aligned {@link android.support.v4.view.PagerTitleStrip} inside it. Individual pages (provided by the adapter) occupy the remaining space inside the {@link android.support.v4.view.ViewPager}.</p>
+
+<pre>
+<android.support.v4.view.ViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.view.PagerTitleStrip
+ android:id="@+id/pager_title_strip"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top"
+ android:background="#33b5e5"
+ android:textColor="#fff"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp" />
+
+</android.support.v4.view.ViewPager>
+</pre>
+
+<h2 id="swipe-tabs">Implement Swiping Between Tabs</h2>
+
+<p>One of the key design recommendations in Android 4.0 for tabs is to <a href="{@docRoot}design/patterns/swipe-views.html">allow swiping</a> between them where appropriate. This behavior enables users to swipe horizontally across the selected tab's contents to navigate to adjacent tabs, without needed to directly interact with the tabs themselves. To implement this, you can use a {@link android.support.v4.view.ViewPager} in conjunction with the {@link android.app.ActionBar} tabs API.</p>
+
+<p>Upon observing the current page changing, select the corresponding tab. You can set up this behavior using an {@link android.support.v4.view.ViewPager.OnPageChangeListener} in your activity's {@link android.app.Activity#onCreate onCreate()} method:</p>
+
+<pre>
+{@literal @}Override
+public void onCreate(Bundle savedInstanceState) {
+ ...
+ mViewPager.setOnPageChangeListener(
+ new ViewPager.SimpleOnPageChangeListener() {
+ {@literal @}Override
+ public void onPageSelected(int position) {
+ // When swiping between pages, select the
+ // corresponding tab.
+ getActionBar().setSelectedNavigationItem(position);
+ }
+ });
+ ...
+}
+</pre>
+
+<p>And upon selecting a tab, switch to the corresponding page in the {@link android.support.v4.view.ViewPager}. To do this, add an {@link android.app.ActionBar.TabListener} to your tab when creating it using the {@link android.app.ActionBar#newTab newTab()} method:</p>
+
+<pre>
+actionBar.newTab()
+ ...
+ .setTabListener(new ActionBar.TabListener() {
+ public void onTabSelected(ActionBar.Tab tab,
+ FragmentTransaction ft) {
+ // When the tab is selected, switch to the
+ // corresponding page in the ViewPager.
+ mViewPager.setCurrentItem(tab.getPosition());
+ }
+ ...
+ }));
+</pre>
diff --git a/docs/html/training/implementing-navigation/temporal.jd b/docs/html/training/implementing-navigation/temporal.jd
new file mode 100644
index 0000000..f36991f
--- /dev/null
+++ b/docs/html/training/implementing-navigation/temporal.jd
@@ -0,0 +1,83 @@
+page.title=Implementing Temporal Navigation
+parent.title=Implementing Effective Navigation
+parent.link=index.html
+
+trainingnavtop=true
+previous.title=Implementing Ancestral Navigation
+previous.link=ancestral.html
+next.title=Implementing Descendant Navigation
+next.link=descendant.html
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+
+<h2>This lesson teaches you to:</h2>
+<ol>
+ <li><a href="#back-fragments">Implement <em>Back</em> Navigation with Fragments</a></li>
+ <li><a href="#back-webviews">Implement <em>Back</em> Navigation with WebViews</a></li>
+</ol>
+
+<h2>You should also read</h2>
+<ul>
+ <li><a href="{@docRoot}training/design-navigation/ancestral-temporal.html">Providing Ancestral and Temporal Navigation</a></li>
+ <li><a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a></li>
+ <li><a href="{@docRoot}design/patterns/navigation.html">Android Design: Navigation</a></li>
+</ul>
+
+</div>
+</div>
+
+
+<p><em>Temporal navigation</em> is navigation to previously visited screens. Users can visit previous screens by pressing the device <em>Back</em> button. This user interface pattern is described further in <a href="{@docRoot}training/design-navigation/ancestral-temporal.html">Providing Ancestral and Temporal Navigation</a> in <em>Designing Effective Navigation</em> and in <a href="{@docRoot}design/patterns/navigation.html">Android Design: Navigation</a>.</p>
+
+<p>Android handles basic <em>Back</em> navigation for you (see <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> for details on this behavior). This lesson discusses a number of cases where applications should provide specialized logic for the <em>Back</em> button.</p>
+
+
+<h2 id="back-fragments">Implement <em>Back</em> Navigation with Fragments</h2>
+
+<p>When using fragments in your application, individual {@link android.app.FragmentTransaction} objects can represent context changes that should be added to the back stack. For example, if you are implementing a <a href="descendant.html#master-detail">master/detail flow</a> on a handset by swapping out fragments (thus emulating a {@link android.app.Activity#startActivity startActivity()} call), you should ensure that pressing the <em>Back</em> button on a detail screen returns the user to the master screen. To do so, you can use {@link android.app.FragmentTransaction#addToBackStack addToBackStack()}:</p>
+
+<pre>
+// Works with either the framework FragmentManager or the
+// support package FragmentManager (getSupportFragmentManager).
+getFragmentManager().beginTransaction()
+ .add(detailFragment, "detail")
+
+ // Add this transaction to the back stack and commit.
+ .addToBackStack()
+ .commit();
+</pre>
+
+<p>The activity's {@link android.app.FragmentManager} handles <em>Back</em> button presses if there are {@link android.app.FragmentTransaction} objects on the back stack. When this happens, the {@link android.app.FragmentManager} pops the most recent transaction off the back stack and performs the reverse action (e.g., removing a fragment if the transaction added it).</p>
+
+<p>If your application updates other user interface elements to reflect the current state of your fragments, such as the action bar, remember to update the UI when you commit the transaction. You should update your user interface after the fragment manager back stack changes in addition to when you commit the transaction. You can listen for when a <code>FragmentTransaction</code> is reverted by setting up an {@link android.app.FragmentManager.OnBackStackChangedListener}:</p>
+
+<pre>
+getFragmentManager().addOnBackStackChangedListener(
+ new FragmentManager.OnBackStackChangedListener() {
+ public void onBackStackChanged() {
+ // Update your UI here.
+ }
+ });
+</pre>
+
+<h2 id="back-webviews">Implement <em>Back</em> Navigation with WebViews</h2>
+
+<p>If a part of your application is contained in a {@link android.webkit.WebView}, it may be appropriate for <em>Back</em> to traverse browser history. To do so, you can override {@link android.app.Activity#onBackPressed onBackPressed()} and proxy to the <code>WebView</code> if it has history state:</p>
+
+<pre>
+{@literal @}Override
+public void onBackPressed() {
+ if (mWebView.canGoBack()) {
+ mWebView.goBack();
+ return;
+ }
+
+ // Otherwise defer to system default behavior.
+ super.onBackPressed();
+}
+</pre>
+
+<p>Be careful when using this mechanism with highly dynamic web pages that can grow a large history. Pages that generate an extensive history, such as those that make frequent changes to the document hash, may make it tedious for users to get out of your activity.</p>
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 7269a71..b3a8fd7 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -25,34 +25,38 @@
public static final int UNKNOWN = 0;
/**
- * RGB format used for pictures encoded as RGB_565 see
+ * RGB format used for pictures encoded as RGB_565. See
* {@link android.hardware.Camera.Parameters#setPictureFormat(int)}.
*/
public static final int RGB_565 = 4;
/**
- * Android YUV format:
+ * <p>Android YUV format.</p>
*
- * This format is exposed to software decoders and applications.
+ * <p>This format is exposed to software decoders and applications.</p>
*
- * YV12 is a 4:2:0 YCrCb planar format comprised of a WxH Y plane followed
- * by (W/2) x (H/2) Cr and Cb planes.
+ * <p>YV12 is a 4:2:0 YCrCb planar format comprised of a WxH Y plane followed
+ * by (W/2) x (H/2) Cr and Cb planes.</p>
*
- * This format assumes
- * - an even width
- * - an even height
- * - a horizontal stride multiple of 16 pixels
- * - a vertical stride equal to the height
+ * <p>This format assumes
+ * <ul>
+ * <li>an even width</li>
+ * <li>an even height</li>
+ * <li>a horizontal stride multiple of 16 pixels</li>
+ * <li>a vertical stride equal to the height</li>
+ * </ul>
+ * </p>
*
- * y_size = stride * height
- * c_size = ALIGN(stride/2, 16) * height/2
- * size = y_size + c_size * 2
- * cr_offset = y_size
- * cb_offset = y_size + c_size
+ * <pre> y_size = stride * height
+ * c_size = ALIGN(stride/2, 16) * height/2
+ * size = y_size + c_size * 2
+ * cr_offset = y_size
+ * cb_offset = y_size + c_size</pre>
*
- * Whether this format is supported by the camera hardware can be determined
- * by
+ * This format is guaranteed to be supported for camera preview images since
+ * API level 12; for earlier API versions, check
* {@link android.hardware.Camera.Parameters#getSupportedPreviewFormats()}.
+ * </p>
*/
public static final int YV12 = 0x32315659;
diff --git a/graphics/java/android/graphics/Insets.java b/graphics/java/android/graphics/Insets.java
new file mode 100644
index 0000000..c570cd4
--- /dev/null
+++ b/graphics/java/android/graphics/Insets.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+/**
+ * An Insets instance holds four integer offsets which describe changes to the four
+ * edges of a Rectangle. By convention, positive values move edges towards the
+ * centre of the rectangle.
+ * <p>
+ * Insets are immutable so may be treated as values.
+ *
+ * @hide
+ */
+public class Insets {
+ public static final Insets NONE = new Insets(0, 0, 0, 0);
+
+ public final int left;
+ public final int top;
+ public final int right;
+ public final int bottom;
+
+ private Insets(int left, int top, int right, int bottom) {
+ this.left = left;
+ this.top = top;
+ this.right = right;
+ this.bottom = bottom;
+ }
+
+ // Factory methods
+
+ /**
+ * Return an Insets instance with the appropriate values.
+ *
+ * @param left the left inset
+ * @param top the top inset
+ * @param right the right inset
+ * @param bottom the bottom inset
+ *
+ * @return Insets instance with the appropriate values
+ */
+ public static Insets of(int left, int top, int right, int bottom) {
+ if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+ return NONE;
+ }
+ return new Insets(left, top, right, bottom);
+ }
+
+ /**
+ * Return an Insets instance with the appropriate values.
+ *
+ * @param r the rectangle from which to take the values
+ *
+ * @return an Insets instance with the appropriate values
+ */
+ public static Insets of(Rect r) {
+ return of(r.left, r.top, r.right, r.bottom);
+ }
+
+ /**
+ * Two Insets instances are equal iff they belong to the same class and their fields are
+ * pairwise equal.
+ *
+ * @param o the object to compare this instance with.
+ *
+ * @return true iff this object is equal {@code o}
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Insets insets = (Insets) o;
+
+ if (bottom != insets.bottom) return false;
+ if (left != insets.left) return false;
+ if (right != insets.right) return false;
+ if (top != insets.top) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = left;
+ result = 31 * result + top;
+ result = 31 * result + right;
+ result = 31 * result + bottom;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "Insets{" +
+ "left=" + left +
+ ", top=" + top +
+ ", right=" + right +
+ ", bottom=" + bottom +
+ '}';
+ }
+}
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index c97785e..f68f9dc 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -21,6 +21,8 @@
import android.text.SpannedString;
import android.text.TextUtils;
+import java.util.Locale;
+
/**
* The Paint class holds the style and color information about how to draw
* geometries, text and bitmaps.
@@ -44,6 +46,8 @@
private float mCompatScaling;
private float mInvCompatScaling;
+ private Locale mLocale;
+
/**
* @hide
*/
@@ -348,6 +352,7 @@
// setHinting(DisplayMetrics.DENSITY_DEVICE >= DisplayMetrics.DENSITY_TV
// ? HINTING_OFF : HINTING_ON);
mCompatScaling = mInvCompatScaling = 1;
+ setTextLocale(Locale.getDefault());
}
/**
@@ -373,6 +378,7 @@
mHasCompatScaling = false;
mCompatScaling = mInvCompatScaling = 1;
mBidiFlags = BIDI_DEFAULT_LTR;
+ setTextLocale(Locale.getDefault());
}
/**
@@ -412,6 +418,7 @@
shadowColor = paint.shadowColor;
mBidiFlags = paint.mBidiFlags;
+ mLocale = paint.mLocale;
}
/** @hide */
@@ -1045,6 +1052,50 @@
}
/**
+ * Get the text Locale.
+ *
+ * @return the paint's Locale used for drawing text, never null.
+ */
+ public Locale getTextLocale() {
+ return mLocale;
+ }
+
+ /**
+ * Set the text locale.
+ *
+ * The text locale affects how the text is drawn for some languages.
+ *
+ * For example, if the locale is {@link Locale#CHINESE} or {@link Locale#CHINA},
+ * then the text renderer will prefer to draw text using a Chinese font. Likewise,
+ * if the locale is {@link Locale#JAPANESE} or {@link Locale#JAPAN}, then the text
+ * renderer will prefer to draw text using a Japanese font.
+ *
+ * This distinction is important because Chinese and Japanese text both use many
+ * of the same Unicode code points but their appearance is subtly different for
+ * each language.
+ *
+ * By default, the text locale is initialized to the system locale (as returned
+ * by {@link Locale#getDefault}). This assumes that the text to be rendered will
+ * most likely be in the user's preferred language.
+ *
+ * If the actual language of the text is known, then it can be provided to the
+ * text renderer using this method. The text renderer may attempt to guess the
+ * language script based on the contents of the text to be drawn independent of
+ * the text locale here. Specifying the text locale just helps it do a better
+ * job in certain ambiguous cases
+ *
+ * @param locale the paint's locale value for drawing text, must not be null.
+ */
+ public void setTextLocale(Locale locale) {
+ if (locale == null) {
+ throw new IllegalArgumentException("locale cannot be null");
+ }
+ if (locale.equals(mLocale)) return;
+ mLocale = locale;
+ native_setTextLocale(mNativePaint, locale.toString());
+ }
+
+ /**
* Return the paint's text size.
*
* @return the paint's text size.
@@ -2144,6 +2195,9 @@
private static native void native_setTextAlign(int native_object,
int align);
+ private static native void native_setTextLocale(int native_object,
+ String locale);
+
private static native int native_getTextWidths(int native_object,
char[] text, int index, int count, float[] widths);
private static native int native_getTextWidths(int native_object,
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index e101581..4beaecd 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -155,21 +155,23 @@
/**
* Update the texture image to the most recent frame from the image stream. This may only be
- * called while the OpenGL ES context that owns the texture is bound to the thread. It will
- * implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+ * called while the OpenGL ES context that owns the texture is current on the calling thread.
+ * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
*/
public void updateTexImage() {
- int err = nativeUpdateTexImage();
- if (err != 0) {
- throw new RuntimeException("Error during updateTexImage (see logcat for details)");
- }
+ nativeUpdateTexImage();
}
/**
- * Detach the SurfaceTexture from the OpenGL ES context with which it is currently associated.
- * This can be used to change from one OpenGL ES context to another.
+ * Detach the SurfaceTexture from the OpenGL ES context that owns the OpenGL ES texture object.
+ * This call must be made with the OpenGL ES context current on the calling thread. The OpenGL
+ * ES texture object will be deleted as a result of this call. After calling this method all
+ * calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
+ * a successful call to {@link #attachToGLContext} is made.
*
- * @hide
+ * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
+ * contexts. Note, however, that the image contents are only accessible from one OpenGL ES
+ * context at a time.
*/
public void detachFromGLContext() {
int err = nativeDetachFromGLContext();
@@ -179,8 +181,17 @@
}
/**
+ * Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread. A
+ * new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
+ * that was current at the time of the last call to {@link #detachFromGLContext}. This new
+ * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
*
- * @hide
+ * This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
+ * contexts. Note, however, that the image contents are only accessible from one OpenGL ES
+ * context at a time.
+ *
+ * @param texName The name of the OpenGL ES texture that will be created. This texture name
+ * must be unusued in the OpenGL ES context that is current on the calling thread.
*/
public void attachToGLContext(int texName) {
int err = nativeAttachToGLContext(texName);
@@ -292,7 +303,7 @@
private native void nativeGetTransformMatrix(float[] mtx);
private native long nativeGetTimestamp();
private native void nativeSetDefaultBufferSize(int width, int height);
- private native int nativeUpdateTexImage();
+ private native void nativeUpdateTexImage();
private native int nativeDetachFromGLContext();
private native int nativeAttachToGLContext(int texName);
private native int nativeGetQueuedCount();
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 86e824b..7d1942a 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -705,6 +705,20 @@
}
/**
+ * Return in insets the layout insets suggested by this Drawable for use with alignment
+ * operations during layout. Positive values move toward the
+ * center of the Drawable. Returns true if this drawable
+ * actually has a layout insets, else false. When false is returned, the padding
+ * is always set to 0.
+ *
+ * @hide
+ */
+ public boolean getLayoutInsets(Rect insets) {
+ insets.set(0, 0, 0, 0);
+ return false;
+ }
+
+ /**
* Make this drawable mutable. This operation cannot be reversed. A mutable
* drawable is guaranteed to not share its state with any other drawable.
* This is especially useful when you need to modify properties of drawables
@@ -965,9 +979,7 @@
Rect pad, Rect layoutBounds, String srcName) {
if (np != null) {
- NinePatchDrawable npd = new NinePatchDrawable(res, bm, np, pad, srcName);
- npd.setLayoutBounds(layoutBounds);
- return npd;
+ return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
}
return new BitmapDrawable(res, bm);
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index b0f7fd3..e10f9e8 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -90,6 +90,18 @@
}
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean getLayoutInsets(Rect insets) {
+ if (mCurrDrawable != null) {
+ return mCurrDrawable.getLayoutInsets(insets);
+ } else {
+ return super.getLayoutInsets(insets);
+ }
+ }
+
@Override
public void setAlpha(int alpha) {
if (mAlpha != alpha) {
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 1272071..e502b7a 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -16,9 +16,17 @@
package android.graphics.drawable;
-import android.graphics.*;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.NinePatch;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@@ -29,7 +37,7 @@
import java.io.InputStream;
/**
- *
+ *
* A resizeable bitmap, with stretchable areas that you define. This type of image
* is defined in a .png file with a special format.
*
@@ -47,7 +55,6 @@
private NinePatchState mNinePatchState;
private NinePatch mNinePatch;
private Rect mPadding;
- private Rect mLayoutBounds;
private Paint mPaint;
private boolean mMutated;
@@ -56,7 +63,7 @@
// These are scaled to match the target density.
private int mBitmapWidth;
private int mBitmapHeight;
-
+
NinePatchDrawable() {
}
@@ -69,7 +76,7 @@
public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
}
-
+
/**
* Create drawable from raw nine-patch data, setting initial target density
* based on the display metrics of the resources.
@@ -79,7 +86,19 @@
this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
-
+
+ /**
+ * Create drawable from raw nine-patch data, setting initial target density
+ * based on the display metrics of the resources.
+ *
+ * @hide
+ */
+ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
+ Rect padding, Rect layoutInsets, String srcName) {
+ this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, layoutInsets), res);
+ mNinePatchState.mTargetDensity = mTargetDensity;
+ }
+
/**
* Create drawable from existing nine-patch, not dealing with density.
* @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
@@ -99,13 +118,6 @@
mNinePatchState.mTargetDensity = mTargetDensity;
}
- /**
- * @hide
- */
- void setLayoutBounds(Rect layoutBounds) {
- mLayoutBounds = layoutBounds;
- }
-
private void setNinePatchState(NinePatchState state, Resources res) {
mNinePatchState = state;
mNinePatch = state.mNinePatch;
@@ -201,13 +213,26 @@
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
}
-
+
@Override
public boolean getPadding(Rect padding) {
padding.set(mPadding);
return true;
}
+ /**
+ * @hide
+ */
+ @Override
+ public boolean getLayoutInsets(Rect insets) {
+ Rect layoutInsets = mNinePatchState.mLayoutInsets;
+ if (layoutInsets == null) {
+ return super.getLayoutInsets(insets);
+ }
+ insets.set(layoutInsets);
+ return true;
+ }
+
@Override
public void setAlpha(int alpha) {
if (mPaint == null && alpha == 0xFF) {
@@ -217,7 +242,7 @@
getPaint().setAlpha(alpha);
invalidateSelf();
}
-
+
@Override
public void setColorFilter(ColorFilter cf) {
if (mPaint == null && cf == null) {
@@ -267,6 +292,7 @@
options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
final Rect padding = new Rect();
+ final Rect layoutInsets = new Rect();
Bitmap bitmap = null;
try {
@@ -290,7 +316,7 @@
setNinePatchState(new NinePatchState(
new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"),
- padding, dither), r);
+ padding, layoutInsets, dither), r);
mNinePatchState.mTargetDensity = mTargetDensity;
a.recycle();
@@ -344,7 +370,7 @@
public Region getTransparentRegion() {
return mNinePatch.getTransparentRegion(getBounds());
}
-
+
@Override
public ConstantState getConstantState() {
mNinePatchState.mChangingConfigurations = getChangingConfigurations();
@@ -361,27 +387,36 @@
return this;
}
- final static class NinePatchState extends ConstantState {
+ private final static class NinePatchState extends ConstantState {
final NinePatch mNinePatch;
final Rect mPadding;
+ final Rect mLayoutInsets;
final boolean mDither;
int mChangingConfigurations;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
NinePatchState(NinePatch ninePatch, Rect padding) {
- this(ninePatch, padding, DEFAULT_DITHER);
+ this(ninePatch, padding, new Rect(), DEFAULT_DITHER);
}
- NinePatchState(NinePatch ninePatch, Rect rect, boolean dither) {
+ NinePatchState(NinePatch ninePatch, Rect padding, Rect layoutInsets) {
+ this(ninePatch, padding, layoutInsets, DEFAULT_DITHER);
+ }
+
+ NinePatchState(NinePatch ninePatch, Rect rect, Rect layoutInsets, boolean dither) {
mNinePatch = ninePatch;
mPadding = rect;
+ mLayoutInsets = layoutInsets;
mDither = dither;
}
+ // Copy constructor
+
NinePatchState(NinePatchState state) {
mNinePatch = new NinePatch(state.mNinePatch);
// Note we don't copy the padding because it is immutable.
mPadding = state.mPadding;
+ mLayoutInsets = state.mLayoutInsets;
mDither = state.mDither;
mChangingConfigurations = state.mChangingConfigurations;
mTargetDensity = state.mTargetDensity;
@@ -391,12 +426,12 @@
public Drawable newDrawable() {
return new NinePatchDrawable(this, null);
}
-
+
@Override
public Drawable newDrawable(Resources res) {
return new NinePatchDrawable(this, res);
}
-
+
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java
index cd5300d..10ccb87 100644
--- a/graphics/java/android/renderscript/Allocation.java
+++ b/graphics/java/android/renderscript/Allocation.java
@@ -136,7 +136,6 @@
* consumer. This usage will cause the allocation to be created
* read only.
*
- * @hide
*/
public static final int USAGE_IO_INPUT = 0x0020;
@@ -145,7 +144,6 @@
* SurfaceTexture producer. The dimensions and format of the
* SurfaceTexture will be forced to those of the allocation.
*
- * @hide
*/
public static final int USAGE_IO_OUTPUT = 0x0040;
@@ -193,8 +191,8 @@
/**
* Get the element of the type of the Allocation.
*
- * @hide
- * @return Element
+ * @return Element that describes the structure of data in the
+ * allocation
*
*/
public Element getElement() {
@@ -204,8 +202,8 @@
/**
* Get the usage flags of the Allocation.
*
- * @hide
- * @return usage
+ * @return usage flags associated with the allocation. e.g.
+ * script, texture, etc.
*
*/
public int getUsage() {
@@ -215,12 +213,11 @@
/**
* Get the size of the Allocation in bytes.
*
- * @hide
- * @return sizeInBytes
+ * @return size of the Allocation in bytes.
*
*/
- public int getSizeBytes() {
- return mType.getCount() * mType.getElement().getSizeBytes();
+ public int getBytesSize() {
+ return mType.getCount() * mType.getElement().getBytesSize();
}
private void updateCacheInfo(Type t) {
@@ -362,8 +359,6 @@
* Send a buffer to the output stream. The contents of the
* Allocation will be undefined after this operation.
*
- * @hide
- *
*/
public void ioSend() {
if ((mUsage & USAGE_IO_OUTPUT) == 0) {
@@ -385,8 +380,6 @@
/**
* Receive the latest input into the Allocation.
*
- * @hide
- *
*/
public void ioReceive() {
if ((mUsage & USAGE_IO_INPUT) == 0) {
@@ -424,37 +417,37 @@
throw new RSIllegalArgumentException("Allocation kind is " +
mType.getElement().mKind + ", type " +
mType.getElement().mType +
- " of " + mType.getElement().getSizeBytes() +
+ " of " + mType.getElement().getBytesSize() +
" bytes, passed bitmap was " + bc);
}
break;
case ARGB_8888:
if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGBA) ||
- (mType.getElement().getSizeBytes() != 4)) {
+ (mType.getElement().getBytesSize() != 4)) {
throw new RSIllegalArgumentException("Allocation kind is " +
mType.getElement().mKind + ", type " +
mType.getElement().mType +
- " of " + mType.getElement().getSizeBytes() +
+ " of " + mType.getElement().getBytesSize() +
" bytes, passed bitmap was " + bc);
}
break;
case RGB_565:
if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGB) ||
- (mType.getElement().getSizeBytes() != 2)) {
+ (mType.getElement().getBytesSize() != 2)) {
throw new RSIllegalArgumentException("Allocation kind is " +
mType.getElement().mKind + ", type " +
mType.getElement().mType +
- " of " + mType.getElement().getSizeBytes() +
+ " of " + mType.getElement().getBytesSize() +
" bytes, passed bitmap was " + bc);
}
break;
case ARGB_4444:
if ((mType.getElement().mKind != Element.DataKind.PIXEL_RGBA) ||
- (mType.getElement().getSizeBytes() != 2)) {
+ (mType.getElement().getBytesSize() != 2)) {
throw new RSIllegalArgumentException("Allocation kind is " +
mType.getElement().mKind + ", type " +
mType.getElement().mType +
- " of " + mType.getElement().getSizeBytes() +
+ " of " + mType.getElement().getBytesSize() +
" bytes, passed bitmap was " + bc);
}
break;
@@ -583,7 +576,7 @@
*/
public void setFromFieldPacker(int xoff, FieldPacker fp) {
mRS.validate();
- int eSize = mType.mElement.getSizeBytes();
+ int eSize = mType.mElement.getBytesSize();
final byte[] data = fp.getData();
int count = data.length / eSize;
@@ -612,7 +605,7 @@
}
final byte[] data = fp.getData();
- int eSize = mType.mElement.mElements[component_number].getSizeBytes();
+ int eSize = mType.mElement.mElements[component_number].getBytesSize();
eSize *= mType.mElement.mArraySizes[component_number];
if (data.length != eSize) {
@@ -665,7 +658,7 @@
* @param d the source data array
*/
public void copy1DRangeFromUnchecked(int off, int count, int[] d) {
- int dataSize = mType.mElement.getSizeBytes() * count;
+ int dataSize = mType.mElement.getBytesSize() * count;
data1DChecks(off, count, d.length * 4, dataSize);
mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
}
@@ -679,7 +672,7 @@
* @param d the source data array
*/
public void copy1DRangeFromUnchecked(int off, int count, short[] d) {
- int dataSize = mType.mElement.getSizeBytes() * count;
+ int dataSize = mType.mElement.getBytesSize() * count;
data1DChecks(off, count, d.length * 2, dataSize);
mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
}
@@ -693,7 +686,7 @@
* @param d the source data array
*/
public void copy1DRangeFromUnchecked(int off, int count, byte[] d) {
- int dataSize = mType.mElement.getSizeBytes() * count;
+ int dataSize = mType.mElement.getBytesSize() * count;
data1DChecks(off, count, d.length, dataSize);
mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
}
@@ -707,7 +700,7 @@
* @param d the source data array
*/
public void copy1DRangeFromUnchecked(int off, int count, float[] d) {
- int dataSize = mType.mElement.getSizeBytes() * count;
+ int dataSize = mType.mElement.getBytesSize() * count;
data1DChecks(off, count, d.length * 4, dataSize);
mRS.nAllocationData1D(getIDSafe(), off, mSelectedLOD, count, d, dataSize);
}
@@ -1030,30 +1023,6 @@
}
/**
- * @hide
- * This API is hidden and only intended to be used for
- * transitional purposes.
- *
- * @param type renderscript type describing data layout
- * @param mips specifies desired mipmap behaviour for the
- * allocation
- * @param usage bit field specifying how the allocation is
- * utilized
- */
- static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips,
- int usage, int pointer) {
- rs.validate();
- if (type.getID(rs) == 0) {
- throw new RSInvalidStateException("Bad Type");
- }
- int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, pointer);
- if (id == 0) {
- throw new RSRuntimeException("Allocation creation failed.");
- }
- return new Allocation(id, rs, type, usage);
- }
-
- /**
* Creates a renderscript allocation with the size specified by
* the type and no mipmaps generated by default
*
@@ -1194,8 +1163,11 @@
}
/**
+ * For allocations used with io operations, returns the handle
+ * onto a raw buffer that is being managed by the screen
+ * compositor.
*
- * @hide
+ * @return Surface object associated with allocation
*
*/
public Surface getSurface() {
@@ -1203,7 +1175,9 @@
}
/**
- * @hide
+ * Associate a surface for io output with this allocation
+ *
+ * @param sur Surface to associate with allocation
*/
public void setSurface(Surface sur) {
mRS.validate();
diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java
index d75c951..28914ce 100644
--- a/graphics/java/android/renderscript/Element.java
+++ b/graphics/java/android/renderscript/Element.java
@@ -85,13 +85,13 @@
}
/**
- * @hide
* @return element size in bytes
*/
- public int getSizeBytes() {return mSize;}
+ public int getBytesSize() {return mSize;}
/**
- * @hide
+ * Returns the number of vector components. 2 for float2, 4 for
+ * float4, etc.
* @return element vector size
*/
public int getVectorSize() {return mVectorSize;}
@@ -114,10 +114,6 @@
* RS_* objects. 32 bit opaque handles.
*/
public enum DataType {
- /**
- * @hide
- * new enum
- */
NONE (0, 0),
//FLOAT_16 (1, 2),
FLOAT_32 (2, 4),
@@ -150,7 +146,8 @@
RS_PROGRAM_FRAGMENT (1006, 4),
RS_PROGRAM_VERTEX (1007, 4),
RS_PROGRAM_RASTER (1008, 4),
- RS_PROGRAM_STORE (1009, 4);
+ RS_PROGRAM_STORE (1009, 4),
+ RS_FONT (1010, 4);
int mID;
int mSize;
@@ -201,7 +198,10 @@
}
/**
- * @hide
+ * Elements could be simple, such as an int or a float, or a
+ * structure with multiple sub elements, such as a collection of
+ * floats, float2, float4. This function returns zero for simple
+ * elements or the number of sub-elements otherwise.
* @return number of sub-elements in this element
*/
public int getSubElementCount() {
@@ -212,7 +212,8 @@
}
/**
- * @hide
+ * For complex elements, this function will return the
+ * sub-element at index
* @param index index of the sub-element to return
* @return sub-element in this element at given index
*/
@@ -227,7 +228,8 @@
}
/**
- * @hide
+ * For complex elements, this function will return the
+ * sub-element name at index
* @param index index of the sub-element
* @return sub-element in this element at given index
*/
@@ -242,7 +244,9 @@
}
/**
- * @hide
+ * For complex elements, some sub-elements could be statically
+ * sized arrays. This function will return the array size for
+ * sub-element at index
* @param index index of the sub-element
* @return array size of sub-element in this element at given index
*/
@@ -257,7 +261,8 @@
}
/**
- * @hide
+ * This function specifies the location of a sub-element within
+ * the element
* @param index index of the sub-element
* @return offset in bytes of sub-element in this element at given index
*/
@@ -272,7 +277,6 @@
}
/**
- * @hide
* @return element data type
*/
public DataType getDataType() {
@@ -280,7 +284,6 @@
}
/**
- * @hide
* @return element data kind
*/
public DataKind getDataKind() {
@@ -455,6 +458,13 @@
return rs.mElement_PROGRAM_STORE;
}
+ public static Element FONT(RenderScript rs) {
+ if(rs.mElement_FONT == null) {
+ rs.mElement_FONT = createUser(rs, DataType.RS_FONT);
+ }
+ return rs.mElement_FONT;
+ }
+
public static Element A_8(RenderScript rs) {
if(rs.mElement_A_8 == null) {
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index 104d1cd..d9f64c6 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -78,14 +78,20 @@
}
/**
- * @hide
+ * Program object can have zero or more constant allocations
+ * associated with it. This method returns the total count.
+ * @return number of constant input types
*/
public int getConstantCount() {
return mConstants != null ? mConstants.length : 0;
}
/**
- * @hide
+ * Returns the type of the constant buffer used in the program
+ * object. It could be used to query internal elements or create
+ * an allocation to store constant data.
+ * @param slot index of the constant input type to return
+ * @return constant input type
*/
public Type getConstant(int slot) {
if (slot < 0 || slot >= mConstants.length) {
@@ -95,14 +101,17 @@
}
/**
- * @hide
+ * Returns the number of textures used in this program object
+ * @return number of texture inputs
*/
public int getTextureCount() {
return mTextureCount;
}
/**
- * @hide
+ * Returns the type of texture at a given slot. e.g. 2D or Cube
+ * @param slot index of the texture input
+ * @return texture input type
*/
public TextureType getTextureType(int slot) {
if ((slot < 0) || (slot >= mTextureCount)) {
@@ -112,7 +121,10 @@
}
/**
- * @hide
+ * Returns the name of the texture input at a given slot. e.g.
+ * tex0, diffuse, spec
+ * @param slot index of the texture input
+ * @return texture input name
*/
public String getTextureName(int slot) {
if ((slot < 0) || (slot >= mTextureCount)) {
@@ -318,7 +330,6 @@
}
/**
- * @hide
* Adds a texture input to the Program
*
* @param texType describes that the texture to append it (2D,
diff --git a/graphics/java/android/renderscript/ProgramRaster.java b/graphics/java/android/renderscript/ProgramRaster.java
index 93ee0ce..e40751f 100644
--- a/graphics/java/android/renderscript/ProgramRaster.java
+++ b/graphics/java/android/renderscript/ProgramRaster.java
@@ -48,15 +48,16 @@
}
/**
- * @hide
+ * Specifies whether vertices are rendered as screen aligned
+ * elements of a specified size
* @return whether point sprites are enabled
*/
- public boolean getPointSpriteEnabled() {
+ public boolean isPointSpriteEnabled() {
return mPointSprite;
}
/**
- * @hide
+ * Specifies how triangles are culled based on their orientation
* @return cull mode
*/
public CullMode getCullMode() {
diff --git a/graphics/java/android/renderscript/ProgramStore.java b/graphics/java/android/renderscript/ProgramStore.java
index 677dadd..d0fd6e5 100644
--- a/graphics/java/android/renderscript/ProgramStore.java
+++ b/graphics/java/android/renderscript/ProgramStore.java
@@ -150,7 +150,8 @@
}
/**
- * @hide
+ * Returns the function used to test writing into the depth
+ * buffer
* @return depth function
*/
public DepthFunc getDepthFunc() {
@@ -158,47 +159,47 @@
}
/**
- * @hide
- * @return whether depth writes are enabled
+ * Queries whether writes are enabled into the depth buffer
+ * @return depth mask
*/
- public boolean getDepthMaskEnabled() {
+ public boolean isDepthMaskEnabled() {
return mDepthMask;
}
/**
- * @hide
+ * Queries whether red channel is written
* @return red color channel mask
*/
- public boolean getColorMaskREnabled() {
+ public boolean isColorMaskRedEnabled() {
return mColorMaskR;
}
/**
- * @hide
+ * Queries whether green channel is written
* @return green color channel mask
*/
- public boolean getColorMaskGEnabled() {
+ public boolean isColorMaskGreenEnabled() {
return mColorMaskG;
}
/**
- * @hide
+ * Queries whether blue channel is written
* @return blue color channel mask
*/
- public boolean getColorMaskBEnabled() {
+ public boolean isColorMaskBlueEnabled() {
return mColorMaskB;
}
/**
- * @hide
+ * Queries whether alpha channel is written
* @return alpha channel mask
*/
- public boolean getColorMaskAEnabled() {
+ public boolean isColorMaskAlphaEnabled() {
return mColorMaskA;
}
/**
- * @hide
+ * Specifies how the source blending factor is computed
* @return source blend function
*/
public BlendSrcFunc getBlendSrcFunc() {
@@ -206,7 +207,7 @@
}
/**
- * @hide
+ * Specifies how the destination blending factor is computed
* @return destination blend function
*/
public BlendDstFunc getBlendDstFunc() {
@@ -214,10 +215,11 @@
}
/**
- * @hide
+ * Specifies whether colors are dithered before writing into the
+ * framebuffer
* @return whether dither is enabled
*/
- public boolean getDitherEnabled() {
+ public boolean isDitherEnabled() {
return mDither;
}
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index 32c908e..74d666b 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -55,14 +55,15 @@
}
/**
- * @hide
+ * @return number of input attribute elements
*/
public int getInputCount() {
return mInputs != null ? mInputs.length : 0;
}
/**
- * @hide
+ * @param slot location of the input to return
+ * @return input attribute element
*/
public Element getInput(int slot) {
if (slot < 0 || slot >= mInputs.length) {
diff --git a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
index fac4c3d..54f21b8 100644
--- a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -204,7 +204,7 @@
public Constants(RenderScript rs) {
Type constInputType = ProgramVertexFixedFunction.Builder.getConstantInputType(rs);
mAlloc = Allocation.createTyped(rs, constInputType);
- int bufferSize = constInputType.getElement().getSizeBytes()*
+ int bufferSize = constInputType.getElement().getBytesSize()*
constInputType.getCount();
mIOBuffer = new FieldPacker(bufferSize);
mModel = new Matrix4f();
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 03294b5..abbcdd9 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -664,6 +664,7 @@
Element mElement_PROGRAM_VERTEX;
Element mElement_PROGRAM_RASTER;
Element mElement_PROGRAM_STORE;
+ Element mElement_FONT;
Element mElement_A_8;
Element mElement_RGB_565;
diff --git a/graphics/java/android/renderscript/Sampler.java b/graphics/java/android/renderscript/Sampler.java
index 0a3c91d..0df1012 100644
--- a/graphics/java/android/renderscript/Sampler.java
+++ b/graphics/java/android/renderscript/Sampler.java
@@ -59,7 +59,6 @@
}
/**
- * @hide
* @return minification setting for the sampler
*/
public Value getMinification() {
@@ -67,7 +66,6 @@
}
/**
- * @hide
* @return magnification setting for the sampler
*/
public Value getMagnification() {
@@ -75,7 +73,6 @@
}
/**
- * @hide
* @return S wrapping mode for the sampler
*/
public Value getWrapS() {
@@ -83,7 +80,6 @@
}
/**
- * @hide
* @return T wrapping mode for the sampler
*/
public Value getWrapT() {
@@ -91,7 +87,6 @@
}
/**
- * @hide
* @return anisotropy setting for the sampler
*/
public float getAnisotropy() {
@@ -288,7 +283,7 @@
public Sampler create() {
mRS.validate();
- int id = mRS.nSamplerCreate(mMag.mID, mMin.mID,
+ int id = mRS.nSamplerCreate(mMag.mID, mMin.mID,
mWrapS.mID, mWrapT.mID, mWrapR.mID, mAniso);
Sampler sampler = new Sampler(id, mRS);
sampler.mMin = mMin;
diff --git a/include/androidfw/InputDevice.h b/include/androidfw/InputDevice.h
index c9554dc..38203af 100644
--- a/include/androidfw/InputDevice.h
+++ b/include/androidfw/InputDevice.h
@@ -66,9 +66,11 @@
float fuzz;
};
- void initialize(int32_t id, const String8& name, const String8& descriptor);
+ void initialize(int32_t id, int32_t generation,
+ const String8& name, const String8& descriptor);
inline int32_t getId() const { return mId; }
+ inline int32_t getGeneration() const { return mGeneration; }
inline const String8 getName() const { return mName; }
inline const String8 getDescriptor() const { return mDescriptor; }
inline uint32_t getSources() const { return mSources; }
@@ -91,17 +93,22 @@
return mKeyCharacterMap;
}
+ inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
+ inline bool hasVibrator() const { return mHasVibrator; }
+
inline const Vector<MotionRange>& getMotionRanges() const {
return mMotionRanges;
}
private:
int32_t mId;
+ int32_t mGeneration;
String8 mName;
String8 mDescriptor;
uint32_t mSources;
int32_t mKeyboardType;
sp<KeyCharacterMap> mKeyCharacterMap;
+ bool mHasVibrator;
Vector<MotionRange> mMotionRanges;
};
diff --git a/include/androidfw/KeyLayoutMap.h b/include/androidfw/KeyLayoutMap.h
index 5408680..e7f22a2 100644
--- a/include/androidfw/KeyLayoutMap.h
+++ b/include/androidfw/KeyLayoutMap.h
@@ -64,7 +64,8 @@
public:
static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
- status_t mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const;
+ status_t mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const;
status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
@@ -78,11 +79,14 @@
uint32_t flags;
};
- KeyedVector<int32_t, Key> mKeys;
+ KeyedVector<int32_t, Key> mKeysByScanCode;
+ KeyedVector<int32_t, Key> mKeysByUsageCode;
KeyedVector<int32_t, AxisInfo> mAxes;
KeyLayoutMap();
+ const Key* getKey(int32_t scanCode, int32_t usageCode) const;
+
class Parser {
KeyLayoutMap* mMap;
Tokenizer* mTokenizer;
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
index 698feb6..d6c49f7 100644
--- a/libs/androidfw/InputDevice.cpp
+++ b/libs/androidfw/InputDevice.cpp
@@ -127,26 +127,31 @@
// --- InputDeviceInfo ---
InputDeviceInfo::InputDeviceInfo() {
- initialize(-1, String8("uninitialized device info"), String8("unknown"));
+ initialize(-1, -1, String8("uninitialized device info"), String8("unknown"));
}
InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
- mId(other.mId), mName(other.mName), mDescriptor(other.mDescriptor),
+ mId(other.mId), mGeneration(other.mGeneration),
+ mName(other.mName), mDescriptor(other.mDescriptor),
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
+ mHasVibrator(other.mHasVibrator),
mMotionRanges(other.mMotionRanges) {
}
InputDeviceInfo::~InputDeviceInfo() {
}
-void InputDeviceInfo::initialize(int32_t id, const String8& name, const String8& descriptor) {
+void InputDeviceInfo::initialize(int32_t id, int32_t generation,
+ const String8& name, const String8& descriptor) {
mId = id;
+ mGeneration = generation;
mName = name;
mDescriptor = descriptor;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mHasVibrator = false;
mMotionRanges.clear();
}
diff --git a/libs/androidfw/KeyLayoutMap.cpp b/libs/androidfw/KeyLayoutMap.cpp
index 1809412..a7c2199 100644
--- a/libs/androidfw/KeyLayoutMap.cpp
+++ b/libs/androidfw/KeyLayoutMap.cpp
@@ -80,32 +80,49 @@
return status;
}
-status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t* keyCode, uint32_t* flags) const {
- ssize_t index = mKeys.indexOfKey(scanCode);
- if (index < 0) {
+status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
+ int32_t* outKeyCode, uint32_t* outFlags) const {
+ const Key* key = getKey(scanCode, usageCode);
+ if (!key) {
#if DEBUG_MAPPING
- ALOGD("mapKey: scanCode=%d ~ Failed.", scanCode);
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Failed.", scanCode, usageCode);
#endif
- *keyCode = AKEYCODE_UNKNOWN;
- *flags = 0;
+ *outKeyCode = AKEYCODE_UNKNOWN;
+ *outFlags = 0;
return NAME_NOT_FOUND;
}
- const Key& k = mKeys.valueAt(index);
- *keyCode = k.keyCode;
- *flags = k.flags;
+ *outKeyCode = key->keyCode;
+ *outFlags = key->flags;
#if DEBUG_MAPPING
- ALOGD("mapKey: scanCode=%d ~ Result keyCode=%d, flags=0x%08x.", scanCode, *keyCode, *flags);
+ ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d, outFlags=0x%08x.",
+ scanCode, usageCode, *outKeyCode, *outFlags);
#endif
return NO_ERROR;
}
+const KeyLayoutMap::Key* KeyLayoutMap::getKey(int32_t scanCode, int32_t usageCode) const {
+ if (usageCode) {
+ ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+ return &mKeysByUsageCode.valueAt(index);
+ }
+ }
+ if (scanCode) {
+ ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+ return &mKeysByScanCode.valueAt(index);
+ }
+ }
+ return NULL;
+}
+
status_t KeyLayoutMap::findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const {
- const size_t N = mKeys.size();
+ const size_t N = mKeysByScanCode.size();
for (size_t i=0; i<N; i++) {
- if (mKeys.valueAt(i).keyCode == keyCode) {
- outScanCodes->add(mKeys.keyAt(i));
+ if (mKeysByScanCode.valueAt(i).keyCode == keyCode) {
+ outScanCodes->add(mKeysByScanCode.keyAt(i));
}
}
return NO_ERROR;
@@ -190,7 +207,7 @@
scanCodeToken.string());
return BAD_VALUE;
}
- if (mMap->mKeys.indexOfKey(scanCode) >= 0) {
+ if (mMap->mKeysByScanCode.indexOfKey(scanCode) >= 0) {
ALOGE("%s: Duplicate entry for key scan code '%s'.", mTokenizer->getLocation().string(),
scanCodeToken.string());
return BAD_VALUE;
@@ -231,7 +248,7 @@
Key key;
key.keyCode = keyCode;
key.flags = flags;
- mMap->mKeys.add(scanCode, key);
+ mMap->mKeysByScanCode.add(scanCode, key);
return NO_ERROR;
}
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 9f2bacd..3910739 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -818,7 +818,10 @@
indent[i] = ' ';
}
indent[count] = '\0';
- DISPLAY_LIST_LOGD("%sStart display list (%p, %s)", (char*) indent + 2, this, mName.string());
+ Rect* clipRect = renderer.getClipRect();
+ DISPLAY_LIST_LOGD("%sStart display list (%p, %s), clipRect: %.0f, %.f, %.0f, %.0f",
+ (char*) indent + 2, this, mName.string(), clipRect->left, clipRect->top,
+ clipRect->right, clipRect->bottom);
#endif
renderer.startMark(mName.string());
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 06928df..ebb8eb7 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -520,6 +520,7 @@
layer->texCoords.set(0.0f, bounds.getHeight() / float(layer->getHeight()),
bounds.getWidth() / float(layer->getWidth()), 0.0f);
layer->setColorFilter(mColorFilter);
+ layer->setBlend(true);
// Save the layer in the snapshot
snapshot->flags |= Snapshot::kFlagIsLayer;
@@ -1058,6 +1059,10 @@
return !mSnapshot->clipRect->isEmpty();
}
+Rect* OpenGLRenderer::getClipRect() {
+ return mSnapshot->clipRect;
+}
+
///////////////////////////////////////////////////////////////////////////////
// Drawing commands
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index b52d2b0..47927bb 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -101,6 +101,7 @@
ANDROID_API const Rect& getClipBounds();
ANDROID_API bool quickReject(float left, float top, float right, float bottom);
virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
+ virtual Rect* getClipRect();
virtual status_t drawDisplayList(DisplayList* displayList, uint32_t width, uint32_t height,
Rect& dirty, int32_t flags, uint32_t level = 0);
diff --git a/media/java/android/media/Crypto.java b/media/java/android/media/Crypto.java
deleted file mode 100644
index 43e34fb..0000000
--- a/media/java/android/media/Crypto.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.
- * 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.media;
-
-/**
- * Crypto class can be used in conjunction with MediaCodec to decode
- * encrypted media data.
- * @hide
-*/
-public final class Crypto {
- public static final native boolean isCryptoSchemeSupported(byte[] uuid);
-
- public Crypto(byte[] uuid, byte[] initData) {
- native_setup(uuid, initData);
- }
-
- public final native boolean requiresSecureDecoderComponent(String mime);
-
- @Override
- protected void finalize() {
- native_finalize();
- }
-
- public native final void release();
- private static native final void native_init();
- private native final void native_setup(byte[] uuid, byte[] initData);
- private native final void native_finalize();
-
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
-
- private int mNativeContext;
-}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 410383d..a65c2aa 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -16,7 +16,7 @@
package android.media;
-import android.media.Crypto;
+import android.media.MediaCrypto;
import android.view.Surface;
import java.nio.ByteBuffer;
import java.util.Map;
@@ -24,7 +24,122 @@
/**
* MediaCodec class can be used to access low-level media codec, i.e.
* encoder/decoder components.
- * @hide
+ *
+ * <p>MediaCodec is generally used like this:
+ * <pre>
+ * MediaCodec codec = MediaCodec.createDecoderByType(type);
+ * codec.configure(format, ...);
+ * codec.start();
+ * ByteBuffer[] inputBuffers = codec.getInputBuffers();
+ * ByteBuffer[] outputBuffers = codec.getOutputBuffers();
+ * Map<String, Object> format = codec.getOutputFormat();
+ * for (;;) {
+ * int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
+ * if (inputBufferIndex >= 0) {
+ * // fill inputBuffers[inputBufferIndex] with valid data
+ * ...
+ * codec.queueInputBuffer(inputBufferIndex, ...);
+ * }
+ *
+ * int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs);
+ * if (outputBufferIndex >= 0) {
+ * // outputBuffer is ready to be processed or rendered.
+ * ...
+ * codec.releaseOutputBuffer(outputBufferIndex, ...);
+ * } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ * outputBuffers = codec.getOutputBuffers();
+ * } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ * // Subsequent data will conform to new format.
+ * format = codec.getOutputFormat();
+ * ...
+ * }
+ * }
+ * codec.stop();
+ * codec.release();
+ * codec = null;
+ * </pre>
+ *
+ * Each codec maintains a number of input and output buffers that are
+ * referred to by index in API calls.
+ * The contents of these buffers is represented by the ByteBuffer[] arrays
+ * accessible through getInputBuffers() and getOutputBuffers().
+ *
+ * After a successful call to {@link #start} the client "owns" neither
+ * input nor output buffers, subsequent calls to {@link #dequeueInputBuffer}
+ * and {@link #dequeueOutputBuffer} then transfer ownership from the codec
+ * to the client.<p>
+ * The client is not required to resubmit/release buffers immediately
+ * to the codec, the sample code above simply does this for simplicity's sake.<p>
+ * Once the client has an input buffer available it can fill it with data
+ * and submit it it to the codec via a call to {@link #queueInputBuffer}.<p>
+ * The codec in turn will return an output buffer to the client in response
+ * to {@link #dequeueOutputBuffer}. After the output buffer has been processed
+ * a call to {@link #releaseOutputBuffer} will return it to the codec.
+ * If a video surface has been provided in the call to {@link #configure},
+ * {@link #releaseOutputBuffer} optionally allows rendering of the buffer
+ * to the surface.<p>
+ *
+ * Input buffers (for decoders) and Output buffers (for encoders) contain
+ * encoded data according to the format's type. For video types this data
+ * is all the encoded data representing a single moment in time, for audio
+ * data this is slightly relaxed in that a buffer may contain multiple
+ * encoded frames of audio. In either case, buffers do not start and end on
+ * arbitrary byte boundaries, this is not a stream of bytes, it's a stream
+ * of access units.<p>
+ *
+ * Most formats also require the actual data to be prefixed by a number
+ * of buffers containing setup data, or codec specific data, i.e. the
+ * first few buffers submitted to the codec object after starting it must
+ * be codec specific data marked as such using the flag {@link #FLAG_CODECCONFIG}
+ * in a call to {@link #queueInputBuffer}.
+ *
+ * Once the client reaches the end of the input data it signals the end of
+ * the input stream by specifying a flag of {@link #FLAG_EOS} in the call to
+ * {@link #queueInputBuffer}. The codec will continue to return output buffers
+ * until it eventually signals the end of the output stream by specifying
+ * the same flag ({@link #FLAG_EOS}) on the BufferInfo returned in
+ * {@link #dequeueOutputBuffer}.
+ *
+ * In order to start decoding data that's not adjacent to previously submitted
+ * data (i.e. after a seek) it is necessary to {@link #flush} the decoder.
+ * Any input or output buffers the client may own at the point of the flush are
+ * immediately revoked, i.e. after a call to {@link #flush} the client does not
+ * own any buffers anymore.
+ * Note that the format of the data submitted after a flush must not change,
+ * flush does not support format discontinuities,
+ * for this a full stop(), configure(), start() cycle is necessary.
+ *
+ * The format of the media data is specified as string/value pairs represented
+ * as a Map<String, Object>.<p>
+ *
+ * Fields common to all formats:
+ *
+ * <table>
+ * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr>
+ * <tr><td>mime</td><td>String</td><td>The type of the format.</td></tr>
+ * <tr><td>max-input-size</td><td>Integer</td><td>optional, maximum size of a buffer of input data</td></tr>
+ * <tr><td>bitrate</td><td>Integer</td><td><b>encoder-only</b>, desired bitrate in bits/second</td></tr>
+ * </table>
+ *
+ * Video formats have the following fields:
+ * <table>
+ * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr>
+ * <tr><td>width</td><td>Integer</td><td></td></tr>
+ * <tr><td>height</td><td>Integer</td><td></td></tr>
+ * <tr><td>color-format</td><td>Integer</td><td><b>encoder-only</b></td></tr>
+ * <tr><td>frame-rate</td><td>Integer or Float</td><td><b>encoder-only</b></td></tr>
+ * <tr><td>i-frame-interval</td><td>Integer</td><td><b>encoder-only</b></td></tr>
+ * <tr><td>stride</td><td>Integer</td><td><b>encoder-only</b>, optional, defaults to width</td></tr>
+ * <tr><td>slice-height</td><td>Integer</td><td><b>encoder-only</b>, optional, defaults to height</td></tr>
+ * </table>
+ *
+ * Audio formats have the following fields:
+ * <table>
+ * <tr><th>Name</th><th>Value Type</th><th>Description</th></tr>
+ * <tr><td>channel-count</td><td>Integer</td><td></td></tr>
+ * <tr><td>sample-rate</td><td>Integer</td><td></td></tr>
+ * </table>
+ *
*/
final public class MediaCodec {
/** Per buffer metadata includes an offset and size specifying
@@ -32,43 +147,62 @@
*/
public final static class BufferInfo {
public void set(
- int offset, int size, long timeUs, int flags) {
- mOffset = offset;
- mSize = size;
- mPresentationTimeUs = timeUs;
- mFlags = flags;
+ int newOffset, int newSize, long newTimeUs, int newFlags) {
+ offset = newOffset;
+ size = newSize;
+ presentationTimeUs = newTimeUs;
+ flags = newFlags;
}
- public int mOffset;
- public int mSize;
- public long mPresentationTimeUs;
- public int mFlags;
+ public int offset;
+ public int size;
+ public long presentationTimeUs;
+ public int flags;
};
// The follow flag constants MUST stay in sync with their equivalents
// in MediaCodec.h !
- public static int FLAG_SYNCFRAME = 1;
- public static int FLAG_CODECCONFIG = 2;
- public static int FLAG_EOS = 4;
+
+ /** This indicates that the buffer marked as such contains the data
+ for a sync frame.
+ */
+ public static final int FLAG_SYNCFRAME = 1;
+
+ /** This indicated that the buffer marked as such contains codec
+ initialization / codec specific data instead of media data.
+ */
+ public static final int FLAG_CODECCONFIG = 2;
+
+ /** This signals the end of stream, i.e. no buffers will be available
+ after this, unless of course, {@link #flush} follows.
+ */
+ public static final int FLAG_EOS = 4;
// The following mode constants MUST stay in sync with their equivalents
// in media/hardware/CryptoAPI.h !
- public static int MODE_UNENCRYPTED = 0;
- public static int MODE_AES_CTR = 1;
+ public static final int MODE_UNENCRYPTED = 0;
+ public static final int MODE_AES_CTR = 1;
- /** Instantiate a codec component by mime type. For decoder components
- this is the mime type of media that this decoder should be able to
- decoder, for encoder components it's the type of media this encoder
- should encode _to_.
+ /** Instantiate a decoder supporting input data of the given mime type.
+ * @param type The mime type of the input data.
*/
- public static MediaCodec CreateByType(String type, boolean encoder) {
- return new MediaCodec(type, true /* nameIsType */, encoder);
+ public static MediaCodec createDecoderByType(String type) {
+ return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
+ }
+
+ /** Instantiate an encoder supporting output data of the given mime type.
+ * @param type The desired mime type of the output data.
+ */
+ public static MediaCodec createEncoderByType(String type) {
+ return new MediaCodec(type, true /* nameIsType */, true /* encoder */);
}
/** If you know the exact name of the component you want to instantiate
use this method to instantiate it. Use with caution.
+ Likely to be used with information obtained from {@link android.media.MediaCodecList}
+ @param name The name of the codec to be instantiated.
*/
- public static MediaCodec CreateByComponentName(String name) {
+ public static MediaCodec createByCodecName(String name) {
return new MediaCodec(
name, false /* nameIsType */, false /* unused */);
}
@@ -88,35 +222,14 @@
// to do this for you at some point in the future.
public native final void release();
+ /** If this codec is to be used as an encoder, pass this flag.
+ */
public static int CONFIGURE_FLAG_ENCODE = 1;
/** Configures a component.
- * @param format A map of string/value pairs describing the input format
- * (decoder) or the desired output format.
*
- * Video formats have the following fields:
- * "mime" - String
- * "width" - Integer
- * "height" - Integer
- * optional "max-input-size" - Integer
- *
- * Audio formats have the following fields:
- * "mime" - String
- * "channel-count" - Integer
- * "sample-rate" - Integer
- * optional "max-input-size" - Integer
- *
- * If the format is used to configure an encoder, additional
- * fields must be included:
- * "bitrate" - Integer (in bits/sec)
- *
- * for video formats:
- * "color-format" - Integer
- * "frame-rate" - Integer or Float
- * "i-frame-interval" - Integer
- * optional "stride" - Integer, defaults to "width"
- * optional "slice-height" - Integer, defaults to "height"
- *
+ * @param format The format of the input data (decoder) or the desired
+ * format of the output data (encoder).
* @param surface Specify a surface on which to render the output of this
* decoder.
* @param crypto Specify a crypto object to facilitate secure decryption
@@ -126,7 +239,7 @@
*/
public void configure(
Map<String, Object> format,
- Surface surface, Crypto crypto, int flags) {
+ Surface surface, MediaCrypto crypto, int flags) {
String[] keys = null;
Object[] values = null;
@@ -147,18 +260,23 @@
private native final void native_configure(
String[] keys, Object[] values,
- Surface surface, Crypto crypto, int flags);
+ Surface surface, MediaCrypto crypto, int flags);
/** After successfully configuring the component, call start. On return
* you can query the component for its input/output buffers.
*/
public native final void start();
+ /** Finish the decode/encode session, note that the codec instance
+ * remains active and ready to be {@link #start}ed again.
+ * To ensure that it is available to other client call {@link #release}
+ * and don't just rely on garbage collection to eventually do this for you.
+ */
public native final void stop();
/** Flush both input and output ports of the component, all indices
- * previously returned in calls to dequeueInputBuffer and
- * dequeueOutputBuffer become invalid.
+ * previously returned in calls to {@link #dequeueInputBuffer} and
+ * {@link #dequeueOutputBuffer} become invalid.
*/
public native final void flush();
@@ -169,24 +287,36 @@
* preceded by "codec specific data", i.e. setup data used to initialize
* the codec such as PPS/SPS in the case of AVC video or code tables
* in the case of vorbis audio.
- * The class MediaExtractor provides codec specific data as part of
+ * The class {@link android.media.MediaExtractor} provides codec
+ * specific data as part of
* the returned track format in entries named "csd-0", "csd-1" ...
*
* These buffers should be submitted using the flag {@link #FLAG_CODECCONFIG}.
*
* To indicate that this is the final piece of input data (or rather that
* no more input data follows unless the decoder is subsequently flushed)
- * specify the flag {@link FLAG_EOS}.
+ * specify the flag {@link #FLAG_EOS}.
+ *
+ * @param index The index of a client-owned input buffer previously returned
+ * in a call to {@link #dequeueInputBuffer}.
+ * @param offset The byte offset into the input buffer at which the data starts.
+ * @param size The number of bytes of valid input data.
+ * @param presentationTimeUs The time at which this buffer should be rendered.
+ * @param flags A bitmask of flags {@link #FLAG_SYNCFRAME},
+ * {@link #FLAG_CODECCONFIG} or {@link #FLAG_EOS}.
*/
public native final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags);
- /** Similar to {@link queueInputBuffer} but submits a buffer that is
+ /** Similar to {@link #queueInputBuffer} but submits a buffer that is
* potentially encrypted. The buffer's data is considered to be
* partitioned into "subSamples", each subSample starts with a
* (potentially empty) run of plain, unencrypted bytes followed
* by a (also potentially empty) run of encrypted bytes.
+ * @param index The index of a client-owned input buffer previously returned
+ * in a call to {@link #dequeueInputBuffer}.
+ * @param offset The byte offset into the input buffer at which the data starts.
* @param numBytesOfClearData The number of leading unencrypted bytes in
* each subSample.
* @param numBytesOfEncryptedData The number of trailing encrypted bytes
@@ -212,27 +342,48 @@
long presentationTimeUs,
int flags);
- // Returns the index of an input buffer to be filled with valid data
- // or -1 if no such buffer is currently available.
- // This method will return immediately if timeoutUs == 0, wait indefinitely
- // for the availability of an input buffer if timeoutUs < 0 or wait up
- // to "timeoutUs" microseconds if timeoutUs > 0.
+ /** Returns the index of an input buffer to be filled with valid data
+ * or -1 if no such buffer is currently available.
+ * This method will return immediately if timeoutUs == 0, wait indefinitely
+ * for the availability of an input buffer if timeoutUs < 0 or wait up
+ * to "timeoutUs" microseconds if timeoutUs > 0.
+ * @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite".
+ */
public native final int dequeueInputBuffer(long timeoutUs);
- // Returns the index of an output buffer that has been successfully
- // decoded or one of the INFO_* constants below.
- // The provided "info" will be filled with buffer meta data.
+ /** If a non-negative timeout had been specified in the call
+ * to {@link #dequeueOutputBuffer}, indicates that the call timed out.
+ */
public static final int INFO_TRY_AGAIN_LATER = -1;
+
+ /** The output format has changed, subsequent data will follow the new
+ * format. {@link #getOutputFormat} returns the new format.
+ */
public static final int INFO_OUTPUT_FORMAT_CHANGED = -2;
+
+ /** The output buffers have changed, the client must refer to the new
+ * set of output buffers returned by {@link #getOutputBuffers} from
+ * this point on.
+ */
public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3;
- /** Dequeue an output buffer, block at most "timeoutUs" microseconds. */
+ /** Dequeue an output buffer, block at most "timeoutUs" microseconds.
+ * Returns the index of an output buffer that has been successfully
+ * decoded or one of the INFO_* constants below.
+ * @param info Will be filled with buffer meta data.
+ * @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite".
+ */
public native final int dequeueOutputBuffer(
BufferInfo info, long timeoutUs);
- // If you are done with a buffer, use this call to return the buffer to
- // the codec. If you previously specified a surface when configuring this
- // video decoder you can optionally render the buffer.
+ /** If you are done with a buffer, use this call to return the buffer to
+ * the codec. If you previously specified a surface when configuring this
+ * video decoder you can optionally render the buffer.
+ * @param index The index of a client-owned output buffer previously returned
+ * in a call to {@link #dequeueOutputBuffer}.
+ * @param render If a valid surface was specified when configuring the codec,
+ * passing true renders this output buffer to the surface.
+ */
public native final void releaseOutputBuffer(int index, boolean render);
/** Call this after dequeueOutputBuffer signals a format change by returning
diff --git a/media/java/android/media/MediaCodecList.java b/media/java/android/media/MediaCodecList.java
index b46ce96..1772e9c 100644
--- a/media/java/android/media/MediaCodecList.java
+++ b/media/java/android/media/MediaCodecList.java
@@ -20,22 +20,42 @@
* MediaCodecList class can be used to enumerate available codecs,
* find a codec supporting a given format and query the capabilities
* of a given codec.
- * @hide
*/
final public class MediaCodecList {
+ /** Count the number of available codecs.
+ */
public static native final int countCodecs();
+
+ /** Retrieve the codec name at the specified index. */
public static native final String getCodecName(int index);
+
+ /** Query if the codec at the specified index is an encoder. */
public static native final boolean isEncoder(int index);
+
+ /** Query the media types supported by the codec at the specified index */
public static native final String[] getSupportedTypes(int index);
public static final class CodecProfileLevel {
- public int mProfile;
- public int mLevel;
+ /** Defined in the OpenMAX IL specs, depending on the type of media
+ * this can be OMX_VIDEO_AVCPROFILETYPE, OMX_VIDEO_H263PROFILETYPE
+ * or OMX_VIDEO_MPEG4PROFILETYPE.
+ */
+ public int profile;
+
+ /** Defined in the OpenMAX IL specs, depending on the type of media
+ * this can be OMX_VIDEO_AVCLEVELTYPE, OMX_VIDEO_H263LEVELTYPE
+ * or OMX_VIDEO_MPEG4LEVELTYPE.
+ */
+ public int level;
};
public static final class CodecCapabilities {
- public CodecProfileLevel[] mProfileLevels;
- public int[] mColorFormats;
+ public CodecProfileLevel[] profileLevels;
+
+ /** Defined in the OpenMAX IL specs, color format values are drawn from
+ * OMX_COLOR_FORMATTYPE.
+ */
+ public int[] colorFormats;
};
public static native final CodecCapabilities getCodecCapabilities(
int index, String type);
diff --git a/media/java/android/media/MediaCrypto.java b/media/java/android/media/MediaCrypto.java
new file mode 100644
index 0000000..0c7f6ef
--- /dev/null
+++ b/media/java/android/media/MediaCrypto.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ * 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.media;
+
+/**
+ * MediaCrypto class can be used in conjunction with {@link android.media.MediaCodec}
+ * to decode encrypted media data.
+ *
+ * Crypto schemes are assigned 16 byte UUIDs,
+ * the method {@link #isCryptoSchemeSupported} can be used to query if a given
+ * scheme is supported on the device.
+ *
+*/
+public final class MediaCrypto {
+ /** Query if the given scheme identified by its UUID is supported on
+ * this device.
+ * @param uuid The UUID of the crypto scheme.
+ */
+ public static final native boolean isCryptoSchemeSupported(byte[] uuid);
+
+ /** Instantiate a MediaCrypto object using opaque, crypto scheme specific
+ * data.
+ * @param uuid The UUID of the crypto scheme.
+ * @param initData Opaque initialization data specific to the crypto scheme.
+ */
+ public MediaCrypto(byte[] uuid, byte[] initData) {
+ native_setup(uuid, initData);
+ }
+
+ /** Query if the crypto scheme requires the use of a secure decoder
+ * to decode data of the given mime type.
+ * @param mime The mime type of the media data
+ */
+ public final native boolean requiresSecureDecoderComponent(String mime);
+
+ @Override
+ protected void finalize() {
+ native_finalize();
+ }
+
+ public native final void release();
+ private static native final void native_init();
+ private native final void native_setup(byte[] uuid, byte[] initData);
+ private native final void native_finalize();
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+
+ private int mNativeContext;
+}
diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java
index 9c3b6a7..3f8b2ca 100644
--- a/media/java/android/media/MediaExtractor.java
+++ b/media/java/android/media/MediaExtractor.java
@@ -16,63 +16,227 @@
package android.media;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.net.Uri;
+import java.io.FileDescriptor;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Map;
/**
- * MediaExtractor
- * @hide
+ * MediaExtractor facilitates extraction of demuxed, typically encoded, media data
+ * from a data source.
+ * <p>It is generally used like this:
+ * <pre>
+ * MediaExtractor extractor = new MediaExtractor();
+ * extractor.setDataSource(...);
+ * int numTracks = extractor.countTracks();
+ * for (int i = 0; i < numTracks; ++i) {
+ * Map%lt;String, Object> format = extractor.getTrackFormat(i);
+ * String mime = (String)format.get("mime");
+ * if (weAreInterestedInThisTrack) {
+ * extractor.selectTrack(i);
+ * }
+ * }
+ * ByteBuffer inputBuffer = ByteBuffer.allocate(...)
+ * while (extractor.readSampleData(inputBuffer, ...) >= 0) {
+ * int trackIndex = extractor.getTrackIndex();
+ * long presentationTimeUs = extractor.getSampleTime();
+ * ...
+ * extractor.advance();
+ * }
+ *
+ * extractor.release();
+ * extractor = null;
+ * </pre>
*/
final public class MediaExtractor {
- public MediaExtractor(String path) {
- native_setup(path);
+ public MediaExtractor() {
+ native_setup();
}
+ /**
+ * Sets the data source as a content Uri.
+ *
+ * @param context the Context to use when resolving the Uri
+ * @param uri the Content URI of the data you want to extract from.
+ * @param headers the headers to be sent together with the request for the data
+ */
+ public final void setDataSource(
+ Context context, Uri uri, Map<String, String> headers)
+ throws IOException {
+ String scheme = uri.getScheme();
+ if(scheme == null || scheme.equals("file")) {
+ setDataSource(uri.getPath());
+ return;
+ }
+
+ AssetFileDescriptor fd = null;
+ try {
+ ContentResolver resolver = context.getContentResolver();
+ fd = resolver.openAssetFileDescriptor(uri, "r");
+ if (fd == null) {
+ return;
+ }
+ // Note: using getDeclaredLength so that our behavior is the same
+ // as previous versions when the content provider is returning
+ // a full file.
+ if (fd.getDeclaredLength() < 0) {
+ setDataSource(fd.getFileDescriptor());
+ } else {
+ setDataSource(
+ fd.getFileDescriptor(),
+ fd.getStartOffset(),
+ fd.getDeclaredLength());
+ }
+ return;
+ } catch (SecurityException ex) {
+ } catch (IOException ex) {
+ } finally {
+ if (fd != null) {
+ fd.close();
+ }
+ }
+
+ setDataSource(uri.toString(), headers);
+ }
+
+ /**
+ * Sets the data source (file-path or http URL) to use.
+ *
+ * @param path the path of the file, or the http URL
+ * @param headers the headers associated with the http request for the stream you want to play
+ */
+ public final void setDataSource(String path, Map<String, String> headers) {
+ String[] keys = null;
+ String[] values = null;
+
+ if (headers != null) {
+ keys = new String[headers.size()];
+ values = new String[headers.size()];
+
+ int i = 0;
+ for (Map.Entry<String, String> entry: headers.entrySet()) {
+ keys[i] = entry.getKey();
+ values[i] = entry.getValue();
+ ++i;
+ }
+ }
+ setDataSource(path, keys, values);
+ }
+
+ private native final void setDataSource(
+ String path, String[] keys, String[] values);
+
+ /**
+ * Sets the data source (file-path or http URL) to use.
+ *
+ * @param path the path of the file, or the http URL of the stream
+ *
+ * <p>When <code>path</code> refers to a local file, the file may actually be opened by a
+ * process other than the calling application. This implies that the pathname
+ * should be an absolute path (as any other process runs with unspecified current working
+ * directory), and that the pathname should reference a world-readable file.
+ * As an alternative, the application could first open the file for reading,
+ * and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
+ */
+ public final void setDataSource(String path) {
+ setDataSource(path, null, null);
+ }
+
+ /**
+ * Sets the data source (FileDescriptor) to use. It is the caller's responsibility
+ * to close the file descriptor. It is safe to do so as soon as this call returns.
+ *
+ * @param fd the FileDescriptor for the file you want to extract from.
+ */
+ public final void setDataSource(FileDescriptor fd) {
+ setDataSource(fd, 0, 0x7ffffffffffffffL);
+ }
+
+ /**
+ * Sets the data source (FileDescriptor) to use. The FileDescriptor must be
+ * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+ * to close the file descriptor. It is safe to do so as soon as this call returns.
+ *
+ * @param fd the FileDescriptor for the file you want to extract from.
+ * @param offset the offset into the file where the data to be extracted starts, in bytes
+ * @param length the length in bytes of the data to be extracted
+ */
+ public native final void setDataSource(
+ FileDescriptor fd, long offset, long length);
+
@Override
protected void finalize() {
native_finalize();
}
- // Make sure you call this when you're done to free up any resources
- // instead of relying on the garbage collector to do this for you at
- // some point in the future.
+ /** Make sure you call this when you're done to free up any resources
+ * instead of relying on the garbage collector to do this for you at
+ * some point in the future.
+ */
public native final void release();
+ /** Count the number of tracks found in the data source.
+ */
public native int countTracks();
+
+ /** Get the track format at the specified index.
+ * More detail on the representation can be found at {@link android.media.MediaCodec}
+ */
public native Map<String, Object> getTrackFormat(int index);
- // Subsequent calls to "readSampleData", "getSampleTrackIndex" and
- // "getSampleTime" only retrieve information for the subset of tracks
- // selected by the call below.
- // Selecting the same track multiple times has no effect, the track
- // is only selected once.
+ /** Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
+ * {@link #getSampleTime} only retrieve information for the subset of tracks
+ * selected by the call below.
+ * Selecting the same track multiple times has no effect, the track
+ * only selected once.
+ * Media data will be returned in the order of their timestamps.
+ */
public native void selectTrack(int index);
- // All selected tracks seek near the requested time. The next sample
- // returned for each selected track will be a sync sample.
+ /** All selected tracks seek near the requested time. The next sample
+ * returned for each selected track will be a sync sample.
+ */
public native void seekTo(long timeUs);
+ /** Advance to the next sample. Returns false if no more sample data
+ * is available (end of stream).
+ */
public native boolean advance();
- // Retrieve the current encoded sample and store it in the byte buffer
- // starting at the given offset. Returns the sample size.
+ /** Retrieve the current encoded sample and store it in the byte buffer
+ * starting at the given offset. Returns the sample size (or -1 if
+ * no more samples are available).
+ */
public native int readSampleData(ByteBuffer byteBuf, int offset);
- // Returns the track index the current sample originates from.
+ /** Returns the track index the current sample originates from (or -1
+ * if no more samples are available)
+ */
public native int getSampleTrackIndex();
- // Returns the current sample's presentation time in microseconds.
+ /** Returns the current sample's presentation time in microseconds.
+ * or -1 if no more samples are available.
+ */
public native long getSampleTime();
// Keep these in sync with their equivalents in NuMediaExtractor.h
+ /** The sample is a sync sample */
public static final int SAMPLE_FLAG_SYNC = 1;
+
+ /** The sample is (at least partially) encrypted, see also the documentation
+ * for {@link android.media.MediaCodec#queueSecureInputBuffer}
+ */
public static final int SAMPLE_FLAG_ENCRYPTED = 2;
- // Returns the current sample's flags.
+ /** Returns the current sample's flags. */
public native int getSampleFlags();
private static native final void native_init();
- private native final void native_setup(String path);
+ private native final void native_setup();
private native final void native_finalize();
static {
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index c38f8f3..dd01db6 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -34,7 +34,9 @@
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Map;
@@ -847,8 +849,10 @@
* As an alternative, the application could first open the file for reading,
* and then use the file descriptor form {@link #setDataSource(FileDescriptor)}.
*/
- public native void setDataSource(String path)
- throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+ public void setDataSource(String path)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ setDataSource(path, null, null);
+ }
/**
* Sets the data source (file-path or http/rtsp URL) to use.
@@ -875,7 +879,20 @@
++i;
}
}
- _setDataSource(path, keys, values);
+ setDataSource(path, keys, values);
+ }
+
+ private void setDataSource(String path, String[] keys, String[] values)
+ throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+ File file = new File(path);
+ if (file.exists()) {
+ FileInputStream is = new FileInputStream(file);
+ FileDescriptor fd = is.getFD();
+ setDataSource(fd);
+ is.close();
+ } else {
+ _setDataSource(path, keys, values);
+ }
}
private native void _setDataSource(
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index a3361d4..98e1bc5 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -2,7 +2,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- android_media_Crypto.cpp \
+ android_media_MediaCrypto.cpp \
android_media_MediaCodec.cpp \
android_media_MediaCodecList.cpp \
android_media_MediaExtractor.cpp \
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 01d3833..979ffb0 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -20,7 +20,7 @@
#include "android_media_MediaCodec.h"
-#include "android_media_Crypto.h"
+#include "android_media_MediaCrypto.h"
#include "android_media_Utils.h"
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/android_view_Surface.h"
@@ -656,7 +656,7 @@
{ "native_configure",
"([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;"
- "Landroid/media/Crypto;I)V",
+ "Landroid/media/MediaCrypto;I)V",
(void *)android_media_MediaCodec_native_configure },
{ "start", "()V", (void *)android_media_MediaCodec_start },
diff --git a/media/jni/android_media_MediaCodecList.cpp b/media/jni/android_media_MediaCodecList.cpp
index 2b8f91e..7139560 100644
--- a/media/jni/android_media_MediaCodecList.cpp
+++ b/media/jni/android_media_MediaCodecList.cpp
@@ -118,10 +118,10 @@
env->NewObjectArray(profileLevels.size(), profileLevelClazz, NULL);
jfieldID profileField =
- env->GetFieldID(profileLevelClazz, "mProfile", "I");
+ env->GetFieldID(profileLevelClazz, "profile", "I");
jfieldID levelField =
- env->GetFieldID(profileLevelClazz, "mLevel", "I");
+ env->GetFieldID(profileLevelClazz, "level", "I");
for (size_t i = 0; i < profileLevels.size(); ++i) {
const MediaCodecList::ProfileLevel &src = profileLevels.itemAt(i);
@@ -139,7 +139,7 @@
jfieldID profileLevelsField = env->GetFieldID(
capsClazz,
- "mProfileLevels",
+ "profileLevels",
"[Landroid/media/MediaCodecList$CodecProfileLevel;");
env->SetObjectField(caps, profileLevelsField, profileLevelArray);
@@ -155,7 +155,7 @@
}
jfieldID colorFormatsField = env->GetFieldID(
- capsClazz, "mColorFormats", "[I");
+ capsClazz, "colorFormats", "[I");
env->SetObjectField(caps, colorFormatsField, colorFormatsArray);
diff --git a/media/jni/android_media_Crypto.cpp b/media/jni/android_media_MediaCrypto.cpp
similarity index 80%
rename from media/jni/android_media_Crypto.cpp
rename to media/jni/android_media_MediaCrypto.cpp
index e1a60a1..b0ba307 100644
--- a/media/jni/android_media_Crypto.cpp
+++ b/media/jni/android_media_MediaCrypto.cpp
@@ -15,10 +15,10 @@
*/
//#define LOG_NDEBUG 0
-#define LOG_TAG "Crypto-JNI"
+#define LOG_TAG "MediaCrypto-JNI"
#include <utils/Log.h>
-#include "android_media_Crypto.h"
+#include "android_media_MediaCrypto.h"
#include "android_runtime/AndroidRuntime.h"
#include "jni.h"
@@ -124,7 +124,7 @@
// static
sp<ICrypto> JCrypto::GetCrypto(JNIEnv *env, jobject obj) {
- jclass clazz = env->FindClass("android/media/Crypto");
+ jclass clazz = env->FindClass("android/media/MediaCrypto");
CHECK(clazz != NULL);
if (!env->IsInstanceOf(obj, clazz)) {
@@ -158,19 +158,19 @@
return old;
}
-static void android_media_Crypto_release(JNIEnv *env, jobject thiz) {
+static void android_media_MediaCrypto_release(JNIEnv *env, jobject thiz) {
setCrypto(env, thiz, NULL);
}
-static void android_media_Crypto_native_init(JNIEnv *env) {
- jclass clazz = env->FindClass("android/media/Crypto");
+static void android_media_MediaCrypto_native_init(JNIEnv *env) {
+ jclass clazz = env->FindClass("android/media/MediaCrypto");
CHECK(clazz != NULL);
gFields.context = env->GetFieldID(clazz, "mNativeContext", "I");
CHECK(gFields.context != NULL);
}
-static void android_media_Crypto_native_setup(
+static void android_media_MediaCrypto_native_setup(
JNIEnv *env, jobject thiz,
jbyteArray uuidObj, jbyteArray initDataObj) {
jsize uuidLength = env->GetArrayLength(uuidObj);
@@ -186,16 +186,23 @@
jboolean isCopy;
jbyte *uuid = env->GetByteArrayElements(uuidObj, &isCopy);
- jsize initDataLength = env->GetArrayLength(initDataObj);
- jbyte *initData = env->GetByteArrayElements(initDataObj, &isCopy);
+ jsize initDataLength = 0;
+ jbyte *initData = NULL;
+
+ if (initDataObj != NULL) {
+ initDataLength = env->GetArrayLength(initDataObj);
+ initData = env->GetByteArrayElements(initDataObj, &isCopy);
+ }
sp<JCrypto> crypto = new JCrypto(
env, thiz, (const uint8_t *)uuid, initData, initDataLength);
status_t err = crypto->initCheck();
- env->ReleaseByteArrayElements(initDataObj, initData, 0);
- initData = NULL;
+ if (initDataObj != NULL) {
+ env->ReleaseByteArrayElements(initDataObj, initData, 0);
+ initData = NULL;
+ }
env->ReleaseByteArrayElements(uuidObj, uuid, 0);
uuid = NULL;
@@ -211,12 +218,12 @@
setCrypto(env,thiz, crypto);
}
-static void android_media_Crypto_native_finalize(
+static void android_media_MediaCrypto_native_finalize(
JNIEnv *env, jobject thiz) {
- android_media_Crypto_release(env, thiz);
+ android_media_MediaCrypto_release(env, thiz);
}
-static jboolean android_media_Crypto_isCryptoSchemeSupported(
+static jboolean android_media_MediaCrypto_isCryptoSchemeSupported(
JNIEnv *env, jobject thiz, jbyteArray uuidObj) {
jsize uuidLength = env->GetArrayLength(uuidObj);
@@ -239,7 +246,7 @@
return result;
}
-static jboolean android_media_Crypto_requiresSecureDecoderComponent(
+static jboolean android_media_MediaCrypto_requiresSecureDecoderComponent(
JNIEnv *env, jobject thiz, jstring mimeObj) {
if (mimeObj == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
@@ -268,24 +275,24 @@
}
static JNINativeMethod gMethods[] = {
- { "release", "()V", (void *)android_media_Crypto_release },
- { "native_init", "()V", (void *)android_media_Crypto_native_init },
+ { "release", "()V", (void *)android_media_MediaCrypto_release },
+ { "native_init", "()V", (void *)android_media_MediaCrypto_native_init },
{ "native_setup", "([B[B)V",
- (void *)android_media_Crypto_native_setup },
+ (void *)android_media_MediaCrypto_native_setup },
{ "native_finalize", "()V",
- (void *)android_media_Crypto_native_finalize },
+ (void *)android_media_MediaCrypto_native_finalize },
{ "isCryptoSchemeSupported", "([B)Z",
- (void *)android_media_Crypto_isCryptoSchemeSupported },
+ (void *)android_media_MediaCrypto_isCryptoSchemeSupported },
{ "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z",
- (void *)android_media_Crypto_requiresSecureDecoderComponent },
+ (void *)android_media_MediaCrypto_requiresSecureDecoderComponent },
};
int register_android_media_Crypto(JNIEnv *env) {
return AndroidRuntime::registerNativeMethods(env,
- "android/media/Crypto", gMethods, NELEM(gMethods));
+ "android/media/MediaCrypto", gMethods, NELEM(gMethods));
}
diff --git a/media/jni/android_media_Crypto.h b/media/jni/android_media_MediaCrypto.h
similarity index 100%
rename from media/jni/android_media_Crypto.h
rename to media/jni/android_media_MediaCrypto.h
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 8c661b7..9883962 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -63,8 +63,13 @@
mClass = NULL;
}
-status_t JMediaExtractor::setDataSource(const char *path) {
- return mImpl->setDataSource(path);
+status_t JMediaExtractor::setDataSource(
+ const char *path, const KeyedVector<String8, String8> *headers) {
+ return mImpl->setDataSource(path, headers);
+}
+
+status_t JMediaExtractor::setDataSource(int fd, off64_t offset, off64_t size) {
+ return mImpl->setDataSource(fd, offset, size);
}
size_t JMediaExtractor::countTracks() const {
@@ -200,7 +205,7 @@
if (extractor == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return NULL;
+ return -1;
}
return extractor->countTracks();
@@ -380,24 +385,42 @@
}
static void android_media_MediaExtractor_native_setup(
- JNIEnv *env, jobject thiz, jstring path) {
+ JNIEnv *env, jobject thiz) {
sp<JMediaExtractor> extractor = new JMediaExtractor(env, thiz);
+ setMediaExtractor(env,thiz, extractor);
+}
- if (path == NULL) {
+static void android_media_MediaExtractor_setDataSource(
+ JNIEnv *env, jobject thiz,
+ jstring pathObj, jobjectArray keysArray, jobjectArray valuesArray) {
+ sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+ if (extractor == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (pathObj == NULL) {
jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
return;
}
- const char *tmp = env->GetStringUTFChars(path, NULL);
-
- if (tmp == NULL) {
+ KeyedVector<String8, String8> headers;
+ if (!ConvertKeyValueArraysToKeyedVector(
+ env, keysArray, valuesArray, &headers)) {
return;
}
- status_t err = extractor->setDataSource(tmp);
+ const char *path = env->GetStringUTFChars(pathObj, NULL);
- env->ReleaseStringUTFChars(path, tmp);
- tmp = NULL;
+ if (path == NULL) {
+ return;
+ }
+
+ status_t err = extractor->setDataSource(path, &headers);
+
+ env->ReleaseStringUTFChars(pathObj, path);
+ path = NULL;
if (err != OK) {
jniThrowException(
@@ -406,8 +429,34 @@
"Failed to instantiate extractor.");
return;
}
+}
- setMediaExtractor(env,thiz, extractor);
+static void android_media_MediaExtractor_setDataSourceFd(
+ JNIEnv *env, jobject thiz,
+ jobject fileDescObj, jlong offset, jlong length) {
+ sp<JMediaExtractor> extractor = getMediaExtractor(env, thiz);
+
+ if (extractor == NULL) {
+ jniThrowException(env, "java/lang/IllegalStateException", NULL);
+ return;
+ }
+
+ if (fileDescObj == NULL) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+ return;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, fileDescObj);
+
+ status_t err = extractor->setDataSource(fd, offset, length);
+
+ if (err != OK) {
+ jniThrowException(
+ env,
+ "java/io/IOException",
+ "Failed to instantiate extractor.");
+ return;
+ }
}
static void android_media_MediaExtractor_native_finalize(
@@ -443,11 +492,18 @@
{ "native_init", "()V", (void *)android_media_MediaExtractor_native_init },
- { "native_setup", "(Ljava/lang/String;)V",
+ { "native_setup", "()V",
(void *)android_media_MediaExtractor_native_setup },
{ "native_finalize", "()V",
(void *)android_media_MediaExtractor_native_finalize },
+
+ { "setDataSource", "(Ljava/lang/String;[Ljava/lang/String;"
+ "[Ljava/lang/String;)V",
+ (void *)android_media_MediaExtractor_setDataSource },
+
+ { "setDataSource", "(Ljava/io/FileDescriptor;JJ)V",
+ (void *)android_media_MediaExtractor_setDataSourceFd },
};
int register_android_media_MediaExtractor(JNIEnv *env) {
diff --git a/media/jni/android_media_MediaExtractor.h b/media/jni/android_media_MediaExtractor.h
index 49a64d6..1aacea2 100644
--- a/media/jni/android_media_MediaExtractor.h
+++ b/media/jni/android_media_MediaExtractor.h
@@ -19,7 +19,9 @@
#include <media/stagefright/foundation/ABase.h>
#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
+#include <utils/String8.h>
#include "jni.h"
@@ -30,7 +32,11 @@
struct JMediaExtractor : public RefBase {
JMediaExtractor(JNIEnv *env, jobject thiz);
- status_t setDataSource(const char *path);
+ status_t setDataSource(
+ const char *path,
+ const KeyedVector<String8, String8> *headers);
+
+ status_t setDataSource(int fd, off64_t offset, off64_t size);
size_t countTracks() const;
status_t getTrackFormat(size_t index, jobject *format) const;
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 2e74ffd..5eadb3a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -216,12 +216,6 @@
}
static void
-android_media_MediaPlayer_setDataSource(JNIEnv *env, jobject thiz, jstring path)
-{
- android_media_MediaPlayer_setDataSourceAndHeaders(env, thiz, path, NULL, NULL);
-}
-
-static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
@@ -825,8 +819,6 @@
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
- {"setDataSource", "(Ljava/lang/String;)V", (void *)android_media_MediaPlayer_setDataSource},
-
{
"_setDataSource",
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)V",
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index 1190448..a4d88ff 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -85,6 +85,16 @@
return env->NewObject(clazz, integerConstructID, value);
}
+static jobject makeLongObject(JNIEnv *env, int64_t value) {
+ jclass clazz = env->FindClass("java/lang/Long");
+ CHECK(clazz != NULL);
+
+ jmethodID longConstructID = env->GetMethodID(clazz, "<init>", "(J)V");
+ CHECK(longConstructID != NULL);
+
+ return env->NewObject(clazz, longConstructID, value);
+}
+
static jobject makeFloatObject(JNIEnv *env, float value) {
jclass clazz = env->FindClass("java/lang/Float");
CHECK(clazz != NULL);
@@ -158,6 +168,15 @@
break;
}
+ case AMessage::kTypeInt64:
+ {
+ int64_t val;
+ CHECK(msg->findInt64(key, &val));
+
+ valueObj = makeLongObject(env, val);
+ break;
+ }
+
case AMessage::kTypeFloat:
{
float val;
diff --git a/native/android/native_window.cpp b/native/android/native_window.cpp
index c58ee00..99c0fd3 100644
--- a/native/android/native_window.cpp
+++ b/native/android/native_window.cpp
@@ -60,13 +60,16 @@
int32_t ANativeWindow_setBuffersGeometry(ANativeWindow* window, int32_t width,
int32_t height, int32_t format) {
- int32_t err = native_window_set_buffers_geometry(window, width, height, format);
+ int32_t err = native_window_set_buffers_format(window, format);
if (!err) {
- int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
- if (width && height) {
- mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
- }
- err = native_window_set_scaling_mode(window, mode);
+ err = native_window_set_buffers_user_dimensions(window, width, height);
+ if (!err) {
+ int mode = NATIVE_WINDOW_SCALING_MODE_FREEZE;
+ if (width && height) {
+ mode = NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW;
+ }
+ err = native_window_set_scaling_mode(window, mode);
+ }
}
return err;
}
diff --git a/packages/BackupRestoreConfirmation/AndroidManifest.xml b/packages/BackupRestoreConfirmation/AndroidManifest.xml
index f3feee8..4fb26ae 100644
--- a/packages/BackupRestoreConfirmation/AndroidManifest.xml
+++ b/packages/BackupRestoreConfirmation/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="com.android.backupconfirm" >
<uses-permission android:name="android.permission.BACKUP" />
+ <uses-permission android:name="android.permission.CRYPT_KEEPER" />
<application android:allowClearUserData="false"
android:allowBackup="false"
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
index 7f1d059..82ac8cb 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -265,6 +265,7 @@
} catch (Exception e) {
// If we can't talk to the mount service we have a serious problem; fail
// "secure" i.e. assuming that the device is encrypted.
+ Slog.e(TAG, "Unable to communicate with mount service: " + e.getMessage());
return true;
}
}
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 56826fa..1654eca 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Kennisgewings"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth-verbind"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Stel invoer metodes op"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fisiese sleutelbord"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Laat die program <xliff:g id="APPLICATION">%1$s</xliff:g> toe om toegang tot die USB-toestel te kry?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Laat die program <xliff:g id="APPLICATION">%1$s</xliff:g> toe om toegang tot die USB-toebehoorsel te kry?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Maak <xliff:g id="ACTIVITY">%1$s</xliff:g> oop wanneer hierdie USB-toestel gekoppel is?"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 7013862..42eef13 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"ማሳወቂያዎች"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"ብሉቱዝ አያይዝ"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"የግቤት ስልቶችን አዘጋጅ"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"የሚዳሰስ የቁልፍ ሰሌዳ"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"መተግበሪያ <xliff:g id="APPLICATION">%1$s</xliff:g> የUSB መሣሪያን ለመድረስ ይፍቀድ?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"መተግበሪያ <xliff:g id="APPLICATION">%1$s</xliff:g> የUSB ተቀጥላ ላይ እንዲደርስ ፍቀድ?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"የዚህ USB ተቀጥላ ሲያያዝ <xliff:g id="ACTIVITY">%1$s</xliff:g>ይከፈት?"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 201f72c..9d6051e 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"التنبيهات"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"تم إنشاء الاتصال بالإنترنت عن طريق البلوتوث."</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"إعداد أسلوب الإدخال"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"لوحة مفاتيح فعلية"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى جهاز USB؟"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"هل تريد السماح للتطبيق <xliff:g id="APPLICATION">%1$s</xliff:g> بالدخول إلى ملحق USB؟"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"هل تريد فتح <xliff:g id="ACTIVITY">%1$s</xliff:g> عند توصيل جهاز USB هذا؟"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index b885503..433c124 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Паведамленні"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Прывязаныя праз Bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Налада метадаў уводу"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Фізічная клавіятура"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Дазволіць праыкладанню <xliff:g id="APPLICATION">%1$s</xliff:g> атрымлiваць доступ да прылады USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Дазволіць прыкладанню <xliff:g id="APPLICATION">%1$s</xliff:g> доступ да прылады USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Адкрыць <xliff:g id="ACTIVITY">%1$s</xliff:g>, калі гэтая USB-прылада падлучаная?"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 0ebced0..427abce 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notificacions"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth sense fil"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configura els mètodes d\'entrada"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Teclat físic"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Vols permetre que l\'aplicació <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi al dispositiu USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Vols permetre que l\'aplicació <xliff:g id="APPLICATION">%1$s</xliff:g> accedeixi a l\'accessori USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Vols que s\'obri <xliff:g id="ACTIVITY">%1$s</xliff:g> quan aquest dispositiu USB estigui connectat?"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index ac58b06..6c614cd 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Underretninger"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth-tethering anvendt"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Konfigurer inputmetoder"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fysisk tastatur"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Tillad, at appen <xliff:g id="APPLICATION">%1$s</xliff:g> kan få adgang til USB-enheden?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Vil du tillade, at appen <xliff:g id="APPLICATION">%1$s</xliff:g> får adgang til USB-enheden?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Vil du åbne <xliff:g id="ACTIVITY">%1$s</xliff:g>, når denne USB-enhed er tilsluttet?"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 39d5c33..e3d82b1 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Benachrichtigungen"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth-Tethering aktiv"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Eingabemethoden einrichten"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Physische Tastatur"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"App <xliff:g id="APPLICATION">%1$s</xliff:g> Zugriff auf USB-Gerät gewähren?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"App <xliff:g id="APPLICATION">%1$s</xliff:g> Zugriff auf USB-Zubehör gewähren?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"<xliff:g id="ACTIVITY">%1$s</xliff:g> öffnen, wenn dieses USB-Gerät verbunden ist?"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 48158db..5511b1a 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Ειδοποιήσεις"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Έγινε σύνδεση μέσω Bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Ρύθμιση μεθόδων εισαγωγής"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Φυσικό πληκτρολόγιο"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> η πρόσβαση στη συσκευή USB;"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Να επιτρέπεται στην εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> η πρόσβαση στο αξεσουάρ USB;"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Άνοιγμα του <xliff:g id="ACTIVITY">%1$s</xliff:g> κατά τη σύνδεση αυτής της συσκευής USB;"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 148924a..b20a5f3 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notifications"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth tethered"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Set up input methods"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Physical keyboard"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Allow the app <xliff:g id="APPLICATION">%1$s</xliff:g> to access the USB device?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Allow the app <xliff:g id="APPLICATION">%1$s</xliff:g> to access the USB accessory?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Open <xliff:g id="ACTIVITY">%1$s</xliff:g> when this USB device is connected?"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 4db7130..96c8c23 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notificaciones"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth anclado"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurar métodos de intro."</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Teclado físico"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"¿Deseas que la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda al dispositivo USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"¿Deseas que la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda al accesorio USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"¿Abrir <xliff:g id="ACTIVITY">%1$s</xliff:g> cuando este dispositivo USB esté conectado?"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index f7f73d7..0b3e4e4 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notificaciones"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth anclado"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurar métodos de introducción"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Teclado físico"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"¿Permitir que la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda al dispositivo USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"¿Permitir que la aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> acceda al accesorio USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"¿Quieres abrir <xliff:g id="ACTIVITY">%1$s</xliff:g> al conectar este dispositivo USB?"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 8e3a1e3..055e3ee 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Teatised"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth on jagatud"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Seadista sisestusmeetodeid"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Füüsiline klaviatuur"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Kas lubate rakendusel <xliff:g id="APPLICATION">%1$s</xliff:g> USB-seadmele juurde pääseda?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Kas lubate rakendusel <xliff:g id="APPLICATION">%1$s</xliff:g> USB-seadmele juurde pääseda?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Kas avada <xliff:g id="ACTIVITY">%1$s</xliff:g>, kui see USB-seade on ühendatud?"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 569e929..df25d5e 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"اعلان ها"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"اتصال اینترنتی با بلوتوث تلفن همراه"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"تنظیم روشهای ورودی"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"صفحه کلید فیزیکی"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"به برنامه <xliff:g id="APPLICATION">%1$s</xliff:g> اجازه می دهید به دستگاه USB دسترسی داشته باشد؟"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"به برنامه <xliff:g id="APPLICATION">%1$s</xliff:g> اجازه میدهد تا به وسیله جانبی USB دسترسی داشته باشد؟"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"وقتی این دستگاه USB وصل است، <xliff:g id="ACTIVITY">%1$s</xliff:g> باز شود؟"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 93e6b62..370d234 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Ilmoitukset"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth yhdistetty"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Määritä syöttötavat"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fyysinen näppäimistö"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Annetaanko sovellukselle <xliff:g id="APPLICATION">%1$s</xliff:g> lupa käyttää USB-laitetta?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Annetaanko sovellukselle <xliff:g id="APPLICATION">%1$s</xliff:g> lupa käyttää USB-lisälaitetta?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Avataanko <xliff:g id="ACTIVITY">%1$s</xliff:g> tämän USB-laitteen ollessa kytkettynä?"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 1a8c6a7..c7f307c 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notifications"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Connexion Bluetooth partagée"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurer les modes de saisie"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Clavier physique"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Autoriser l\'application <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder au périphérique USB ?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Autoriser l\'application <xliff:g id="APPLICATION">%1$s</xliff:g> à accéder à l\'accessoire USB ?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Ouvrir <xliff:g id="ACTIVITY">%1$s</xliff:g> lors de la connexion de ce périphérique USB ?"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index 741f1b4..9805f90 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"सूचनाएं"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth टीदर किया गया"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"इनपुट पद्धति सेट करें"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"भौतिक कीबोर्ड"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"एप्लिकेशन <xliff:g id="APPLICATION">%1$s</xliff:g> को USB उपकरण तक पहुंचने दें?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"एप्लिकेशन <xliff:g id="APPLICATION">%1$s</xliff:g> को USB सहायक उपकरण तक पहुंचने दें?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"जब यह USB उपकरण कनेक्ट किया जाए, तब <xliff:g id="ACTIVITY">%1$s</xliff:g> को खोलें?"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 1c28b57..87a9d45 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Értesítések"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth megosztva"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Beviteli módok beállítása"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fizikai billentyűzet"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"A(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazás hozzáférhet az USB-eszközhöz?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"A(z) <xliff:g id="APPLICATION">%1$s</xliff:g> alkalmazás hozzáférhet az USB-kiegészítőhöz?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"<xliff:g id="ACTIVITY">%1$s</xliff:g> megnyitása, ha USB-kiegészítő csatlakoztatva van?"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index cdf7c9d..a725377 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notifiche"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth con tethering"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configura metodi di immissione"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Tastiera fisica"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Consentire all\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere al dispositivo USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Consentire all\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> di accedere all\'accessorio USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Aprire <xliff:g id="ACTIVITY">%1$s</xliff:g> quando questo dispositivo USB è collegato?"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index bc5e873..03fba7e 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"התראות"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth קשור"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"הגדר שיטות קלט"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"מקלדת פיזית"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"לאפשר ליישום <xliff:g id="APPLICATION">%1$s</xliff:g> גישה להתקן ה-USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"לאפשר ליישום <xliff:g id="APPLICATION">%1$s</xliff:g> גישה לאביזר ה-USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"האם לפתוח את <xliff:g id="ACTIVITY">%1$s</xliff:g> כאשר מכשיר USB זה מחובר?"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 01d76eb..4818ca9 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"通知"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetoothテザリング接続"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"入力方法をセットアップ"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"物理キーボード"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"アプリ「<xliff:g id="APPLICATION">%1$s</xliff:g>」にUSBデバイスへのアクセスを許可しますか?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"アプリ「<xliff:g id="APPLICATION">%1$s</xliff:g>」にUSBアクセサリへのアクセスを許可しますか?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"このUSBデバイスが接続されたときに<xliff:g id="ACTIVITY">%1$s</xliff:g>を開きますか?"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 88194df..3321912 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"알림"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"블루투스 테더링됨"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"입력 방법 설정"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"물리적 키보드"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"<xliff:g id="APPLICATION">%1$s</xliff:g> 앱이 USB 기기에 액세스하도록 허용하시겠습니까?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"<xliff:g id="APPLICATION">%1$s</xliff:g> 앱이 USB 액세서리에 액세스하도록 허용하시겠습니까?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"USB 기기가 연결될 때 <xliff:g id="ACTIVITY">%1$s</xliff:g>(을)를 여시겠습니까?"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index 942e159..831fa41 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Pranešimai"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"„Bluetooth“ susieta"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Nustatyti įvesties metodus"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fizinė klaviatūra"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Leisti programai „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti USB įrenginį?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Leisti programai „<xliff:g id="APPLICATION">%1$s</xliff:g>“ pasiekti USB priedą?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Atidaryti <xliff:g id="ACTIVITY">%1$s</xliff:g>, kai prijungtas šis USB įrenginys?"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index b5c9130..158c850 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Paziņojumi"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth piesaiste"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Iestatīt ievades metodes"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fiziskā tastatūra"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Vai ļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt šai USB ierīcei?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Vai ļaut lietotnei <xliff:g id="APPLICATION">%1$s</xliff:g> piekļūt šim USB piederumam?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Vai atvērt darbību <xliff:g id="ACTIVITY">%1$s</xliff:g>, kad tiek pievienota šī USB ierīce?"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index f08013d..899a204 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Pemberitahuan"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth ditambatkan"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Sediakan kaedah input"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Papan kekunci fizikal"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Benarkan aplikasi <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses peranti USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Benarkan aplikasi <xliff:g id="APPLICATION">%1$s</xliff:g> mengakses aksesori USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Buka <xliff:g id="ACTIVITY">%1$s</xliff:g> apabila peranti USB ini disambungkan?"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 5314ba3..e05e2c9 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Meldingen"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth getetherd"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Invoermethoden instellen"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fysiek toetsenbord"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"De app <xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot het USB-apparaat?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"De app <xliff:g id="APPLICATION">%1$s</xliff:g> toegang geven tot het USB-accessoire?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"<xliff:g id="ACTIVITY">%1$s</xliff:g> openen wanneer dit USB-apparaat wordt aangesloten?"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 26e3989..0618eb5 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Powiadomienia"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth – podłączono"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Konfiguruj metody wprowadzania"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Klawiatura fizyczna"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do urządzenia USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Zezwolić aplikacji <xliff:g id="APPLICATION">%1$s</xliff:g> na dostęp do urządzenia USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Czy otworzyć <xliff:g id="ACTIVITY">%1$s</xliff:g> po podłączeniu tego urządzenia USB?"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index a5999e3..6a278ba 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notificações"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth ligado"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurar métodos introdução"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Teclado físico"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Permitir que a aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> aceda ao dispositivo USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Permitir que a aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> aceda ao acessório USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Abrir <xliff:g id="ACTIVITY">%1$s</xliff:g> quando este dispositivo USB estiver ligado?"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 02bef77..23a2ba5 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Notificações"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth vinculado"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Configurar métodos de entrada"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Teclado físico"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Permitir que o aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> acesse o dispositivo USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Permitir que o aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> acesse o acessório USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Abrir <xliff:g id="ACTIVITY">%1$s</xliff:g> quando este dispositivo USB estiver conectado?"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 4057599..3753309 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Уведомления"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Общий модем доступен через Bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Настройка способов ввода"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Физическая клавиатура"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Открыть приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к USB-устройству?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Открыть приложению \"<xliff:g id="APPLICATION">%1$s</xliff:g>\" доступ к USB-устройству?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Запускать <xliff:g id="ACTIVITY">%1$s</xliff:g> при подключении этого USB-устройства?"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 67368ac..599f99c 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Upozornenia"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Zdieľané dátové pripojenie cez Bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Nastavenie metód vstupu"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fyzická klávesnica"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> prístup k zariadeniu USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Povoliť aplikácii <xliff:g id="APPLICATION">%1$s</xliff:g> prístup k periférnemu zariadeniu USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Chcete pri pripojení tohto zariadenia USB otvoriť aplikáciu <xliff:g id="ACTIVITY">%1$s</xliff:g>?"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 77aef3d..fb229e2 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Obvestila"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Internetna povezava prek Bluetootha"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Nastavi načine vnosa"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fizična tipkovnica"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Želite programu <xliff:g id="APPLICATION">%1$s</xliff:g> dovoliti dostop do naprave USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Želite dovoliti programu <xliff:g id="APPLICATION">%1$s</xliff:g> dostop do dodatka USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Želite, da se odpre <xliff:g id="ACTIVITY">%1$s</xliff:g>, ko priključite to napravo USB?"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index ba4edea..8eaa126 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Aviseringar"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Internetdelning via Bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Konfigurera inmatningsmetoder"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fysiskt tangentbord"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Vill du tillåta att appen <xliff:g id="APPLICATION">%1$s</xliff:g> använder USB-enheten?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Vill du tillåta att appen <xliff:g id="APPLICATION">%1$s</xliff:g> använder USB-tillbehöret?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Vill du öppna <xliff:g id="ACTIVITY">%1$s</xliff:g> när den här USB-enheten ansluts?"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 8a6b050..19bd4ab 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -46,8 +46,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Arifa"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth imefungwa"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Weka mbinu za ingizo"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Kibodi halisi"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Ruhusu programu <xliff:g id="APPLICATION">%1$s</xliff:g> kufikia kifaa cha USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Ruhusu programu <xliff:g id="APPLICATION">%1$s</xliff:g> kufikia kifaa cha ziada cha USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Je, ungetaka kufungua <xliff:g id="ACTIVITY">%1$s</xliff:g>wakati kifaa cha USB kimeunganishwa?"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 55ecfae..2054121e 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Mga Notification"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Na-tether ang bluetooth"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"I-set up paraan ng pag-input"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Aktwal na keyboard"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Payagan ang app na <xliff:g id="APPLICATION">%1$s</xliff:g> na i-access ang USB device?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Payagan ang app na <xliff:g id="APPLICATION">%1$s</xliff:g> na i-access ang USB accessory?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Buksan ang <xliff:g id="ACTIVITY">%1$s</xliff:g> kapag nakakonekta ang USB device na ito?"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index f0aa516..ca422f7 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Bildirimler"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth paylaşımı tamam"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Giriş yöntemlerini ayarla"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Fiziksel klavye"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının USB cihazına erişmesine izin verilsin mi?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulamasının USB aksesuarına erişmesine izin verilsin mi?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Bu USB cihaz bağlandığında <xliff:g id="ACTIVITY">%1$s</xliff:g> açılsın mı?"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index abd024a..05ab87c 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Thông báo"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Bluetooth được dùng làm điểm truy cập Internet"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Thiết lập phương thức nhập"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Bàn phím thực"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Cho phép ứng dụng <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập thiết bị USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Cho phép ứng dụng <xliff:g id="APPLICATION">%1$s</xliff:g> truy cập phụ kiện USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Mở <xliff:g id="ACTIVITY">%1$s</xliff:g> khi thiết bị USB này được kết nối?"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index ec92713c..c862d74 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"通知"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"已通过蓝牙共享网络"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"设置输入法"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"物理键盘"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"允许应用“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 设备吗?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"允许应用“<xliff:g id="APPLICATION">%1$s</xliff:g>”访问该 USB 配件吗?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"要在连接此 USB 设备时打开<xliff:g id="ACTIVITY">%1$s</xliff:g>吗?"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 27dd8fd..91f6566 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -48,8 +48,7 @@
<string name="status_bar_settings_notifications" msgid="397146176280905137">"Izaziso"</string>
<string name="bluetooth_tethered" msgid="7094101612161133267">"Ukusebenzisa i-Bluetooth njengemodemu"</string>
<string name="status_bar_input_method_settings_configure_input_methods" msgid="3504292471512317827">"Izilungiselelo zezindlela zokufakwayo"</string>
- <!-- no translation found for status_bar_use_physical_keyboard (7551903084416057810) -->
- <skip />
+ <string name="status_bar_use_physical_keyboard" msgid="7551903084416057810">"Ukwakheka kwekhibhodi"</string>
<string name="usb_device_permission_prompt" msgid="834698001271562057">"Vumela insiza <xliff:g id="APPLICATION">%1$s</xliff:g> ukuthi ufinyelele ezintweni eziphuma ne-USB?"</string>
<string name="usb_accessory_permission_prompt" msgid="5171775411178865750">"Vumela insiza <xliff:g id="APPLICATION">%1$s</xliff:g> ukuthi ufinyelele ezintweni eziphuma ne-USB?"</string>
<string name="usb_device_confirm_prompt" msgid="5161205258635253206">"Vula <xliff:g id="ACTIVITY">%1$s</xliff:g> uma ledivayisi ye-USB ixhunyiwe?"</string>
diff --git a/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
new file mode 100644
index 0000000..d445d5c
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/BiometricSensorUnlock.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.view.View;
+
+interface BiometricSensorUnlock {
+ // Returns 'true' if the biometric sensor is available and is selected by user.
+ public boolean installedAndSelected();
+
+ // Returns 'true' if the biometric sensor has started its unlock procedure but has not yet
+ // accepted or rejected the user.
+ public boolean isRunning();
+
+ // Show the interface, but don't start the unlock procedure. The interface should disappear
+ // after the specified timeout. If the timeout is 0, the interface shows until another event,
+ // such as calling hide(), causes it to disappear.
+ public void show(long timeoutMilliseconds);
+
+ // Hide the interface, if any, exposing the lockscreen.
+ public void hide();
+
+ // Stop the unlock procedure if running. Returns 'true' if it was in fact running.
+ public boolean stop();
+
+ // Start the unlock procedure. Returns ‘false’ if it can’t be started or if the backup should
+ // be used.
+ public boolean start(boolean suppressBiometricUnlock);
+
+ // Provide a view to work within.
+ public void initializeAreaView(View topView);
+
+ // Clean up any resources used by the biometric unlock.
+ public void cleanUp();
+
+ // Returns the Device Policy Manager quality (e.g. PASSWORD_QUALITY_BIOMETRIC_WEAK).
+ public int getQuality();
+}
diff --git a/policy/src/com/android/internal/policy/impl/FaceUnlock.java b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
index 31fbaafd..7b0a086 100644
--- a/policy/src/com/android/internal/policy/impl/FaceUnlock.java
+++ b/policy/src/com/android/internal/policy/impl/FaceUnlock.java
@@ -21,6 +21,7 @@
import com.android.internal.policy.IFaceLockInterface;
import com.android.internal.widget.LockPatternUtils;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -33,7 +34,7 @@
import android.util.Log;
import android.view.View;
-public class FaceUnlock implements Handler.Callback {
+public class FaceUnlock implements BiometricSensorUnlock, Handler.Callback {
private static final boolean DEBUG = false;
private static final String TAG = "FULLockscreen";
@@ -52,10 +53,6 @@
private boolean mServiceRunning = false;
private final Object mServiceRunningLock = new Object();
- // Long enough to stay visible while dialer comes up
- // Short enough to not be visible if the user goes back immediately
- private final int VIEW_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
-
// Long enough to stay visible while the service starts
// Short enough to not have to wait long for backup if service fails to start or crashes
// The service can take a couple of seconds to start on the first try after boot
@@ -80,22 +77,65 @@
mHandler = new Handler(this);
}
- public void cleanUp() {
- if (mService != null) {
- try {
- mService.unregisterCallback(mFaceLockCallback);
- } catch (RemoteException e) {
- // Not much we can do
- }
- stop();
- mService = null;
- }
+ // Indicates whether FaceLock is in use
+ public boolean installedAndSelected() {
+ return (mLockPatternUtils.usingBiometricWeak() &&
+ mLockPatternUtils.isBiometricWeakInstalled());
}
- /** When screen is turned on and focused, need to bind to FaceLock service if we are using
- * FaceLock, but only if we're not dealing with a call
- */
- public void activateIfAble(boolean hasOverlay) {
+ public boolean isRunning() {
+ return mServiceRunning;
+ }
+
+ // Shows the FaceLock area for a period of time
+ public void show(long timeoutMillis) {
+ showArea();
+ if (timeoutMillis > 0)
+ mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
+ }
+
+ // Hides the FaceLock area immediately
+ public void hide() {
+ // Remove messages to prevent a delayed show message from undo-ing the hide
+ removeAreaDisplayMessages();
+ mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
+ }
+
+ // Tells FaceLock to stop and then unbinds from the FaceLock service
+ public boolean stop() {
+ boolean wasRunning = false;
+ if (installedAndSelected()) {
+ stopUi();
+
+ if (mBoundToService) {
+ wasRunning = true;
+ if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceLockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ }
+ mContext.unbindService(mConnection);
+ if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
+ mBoundToService = false;
+ } else {
+ // This is usually not an error when this happens. Sometimes we will tell it to
+ // unbind multiple times because it's called from both onWindowFocusChanged and
+ // onDetachedFromWindow.
+ if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
+ }
+ }
+
+ return wasRunning;
+ }
+
+ /**
+ * When screen is turned on and focused, need to bind to FaceLock service if we are using
+ * FaceLock, but only if we're not dealing with a call
+ */
+ public boolean start(boolean suppressBiometricUnlock) {
final boolean tooManyFaceUnlockTries = mUpdateMonitor.getMaxFaceUnlockAttemptsReached();
final int failedBackupAttempts = mUpdateMonitor.getFailedAttempts();
final boolean backupIsTimedOut =
@@ -103,42 +143,31 @@
if (tooManyFaceUnlockTries) Log.i(TAG, "tooManyFaceUnlockTries: " + tooManyFaceUnlockTries);
if (mUpdateMonitor.getPhoneState() == TelephonyManager.CALL_STATE_IDLE
&& installedAndSelected()
- && !hasOverlay
+ && !suppressBiometricUnlock
&& !tooManyFaceUnlockTries
&& !backupIsTimedOut) {
bind();
// Show FaceLock area, but only for a little bit so lockpattern will become visible if
// FaceLock fails to start or crashes
- showAreaWithTimeout(VIEW_AREA_SERVICE_TIMEOUT);
+ show(VIEW_AREA_SERVICE_TIMEOUT);
// When switching between portrait and landscape view while FaceLock is running, the
// screen will eventually go dark unless we poke the wakelock when FaceLock is
// restarted
mKeyguardScreenCallback.pokeWakelock();
} else {
- hideArea();
+ hide();
+ return false;
}
- }
- public boolean isServiceRunning() {
- return mServiceRunning;
- }
-
- public int viewAreaEmergencyDialerTimeout() {
- return VIEW_AREA_EMERGENCY_DIALER_TIMEOUT;
- }
-
- // Indicates whether FaceLock is in use
- public boolean installedAndSelected() {
- return (mLockPatternUtils.usingBiometricWeak() &&
- mLockPatternUtils.isBiometricWeakInstalled());
+ return true;
}
// Takes care of FaceLock area when layout is created
- public void initializeAreaView(View view) {
+ public void initializeAreaView(View topView) {
if (installedAndSelected()) {
- mAreaView = view.findViewById(R.id.faceLockAreaView);
+ mAreaView = topView.findViewById(R.id.faceLockAreaView);
if (mAreaView == null) {
Log.e(TAG, "Layout does not have areaView and FaceLock is enabled");
}
@@ -147,13 +176,20 @@
}
}
- // Stops FaceLock if it is running and reports back whether it was running or not
- public boolean stopIfRunning() {
- if (installedAndSelected() && mBoundToService) {
- stopAndUnbind();
- return true;
+ public void cleanUp() {
+ if (mService != null) {
+ try {
+ mService.unregisterCallback(mFaceLockCallback);
+ } catch (RemoteException e) {
+ // Not much we can do
+ }
+ stopUi();
+ mService = null;
}
- return false;
+ }
+
+ public int getQuality() {
+ return DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
}
// Handles covering or exposing FaceLock area on the client side when FaceLock starts or stops
@@ -186,28 +222,15 @@
}
// Shows the FaceLock area immediately
- public void showArea() {
+ private void showArea() {
// Remove messages to prevent a delayed hide message from undo-ing the show
removeAreaDisplayMessages();
mHandler.sendEmptyMessage(MSG_SHOW_AREA_VIEW);
}
- // Hides the FaceLock area immediately
- public void hideArea() {
- // Remove messages to prevent a delayed show message from undo-ing the hide
- removeAreaDisplayMessages();
- mHandler.sendEmptyMessage(MSG_HIDE_AREA_VIEW);
- }
-
- // Shows the FaceLock area for a period of time
- public void showAreaWithTimeout(long timeoutMillis) {
- showArea();
- mHandler.sendEmptyMessageDelayed(MSG_HIDE_AREA_VIEW, timeoutMillis);
- }
-
// Binds to FaceLock service. This call does not tell it to start, but it causes the service
// to call the onServiceConnected callback, which then starts FaceLock.
- public void bind() {
+ private void bind() {
if (installedAndSelected()) {
if (!mBoundToService) {
if (DEBUG) Log.d(TAG, "before bind to FaceLock service");
@@ -223,32 +246,6 @@
}
}
- // Tells FaceLock to stop and then unbinds from the FaceLock service
- public void stopAndUnbind() {
- if (installedAndSelected()) {
- stop();
-
- if (mBoundToService) {
- if (DEBUG) Log.d(TAG, "before unbind from FaceLock service");
- if (mService != null) {
- try {
- mService.unregisterCallback(mFaceLockCallback);
- } catch (RemoteException e) {
- // Not much we can do
- }
- }
- mContext.unbindService(mConnection);
- if (DEBUG) Log.d(TAG, "after unbind from FaceLock service");
- mBoundToService = false;
- } else {
- // This is usually not an error when this happens. Sometimes we will tell it to
- // unbind multiple times because it's called from both onWindowFocusChanged and
- // onDetachedFromWindow.
- if (DEBUG) Log.d(TAG, "Attempt to unbind from FaceLock when not bound");
- }
- }
- }
-
private ServiceConnection mConnection = new ServiceConnection() {
// Completes connection, registers callback and starts FaceLock when service is bound
@Override
@@ -268,7 +265,7 @@
int[] position;
position = new int[2];
mAreaView.getLocationInWindow(position);
- start(mAreaView.getWindowToken(), position[0], position[1],
+ startUi(mAreaView.getWindowToken(), position[0], position[1],
mAreaView.getWidth(), mAreaView.getHeight());
}
}
@@ -286,7 +283,7 @@
};
// Tells the FaceLock service to start displaying its UI and perform recognition
- public void start(IBinder windowToken, int x, int y, int w, int h) {
+ private void startUi(IBinder windowToken, int x, int y, int w, int h) {
if (installedAndSelected()) {
synchronized (mServiceRunningLock) {
if (!mServiceRunning) {
@@ -300,14 +297,14 @@
}
mServiceRunning = true;
} else {
- if (DEBUG) Log.w(TAG, "start() attempted while running");
+ if (DEBUG) Log.w(TAG, "startUi() attempted while running");
}
}
}
}
// Tells the FaceLock service to stop displaying its UI and stop recognition
- public void stop() {
+ private void stopUi() {
if (installedAndSelected()) {
// Note that attempting to stop FaceLock when it's not running is not an issue.
// FaceLock can return, which stops it and then we try to stop it when the
@@ -333,7 +330,7 @@
public void unlock() {
if (DEBUG) Log.d(TAG, "FaceLock unlock()");
showArea(); // Keep fallback covered
- stopAndUnbind();
+ stop();
mKeyguardScreenCallback.keyguardDone(true);
mKeyguardScreenCallback.reportSuccessfulUnlockAttempt();
@@ -344,8 +341,8 @@
@Override
public void cancel() {
if (DEBUG) Log.d(TAG, "FaceLock cancel()");
- hideArea(); // Expose fallback
- stopAndUnbind();
+ hide(); // Expose fallback
+ stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
@@ -355,8 +352,8 @@
public void reportFailedAttempt() {
if (DEBUG) Log.d(TAG, "FaceLock reportFailedAttempt()");
mUpdateMonitor.reportFailedFaceUnlockAttempt();
- hideArea(); // Expose fallback
- stopAndUnbind();
+ hide(); // Expose fallback
+ stop();
mKeyguardScreenCallback.pokeWakelock(BACKUP_LOCK_TIMEOUT);
}
@@ -364,7 +361,7 @@
@Override
public void exposeFallback() {
if (DEBUG) Log.d(TAG, "FaceLock exposeFallback()");
- hideArea(); // Expose fallback
+ hide(); // Expose fallback
}
// Allows the Face Unlock service to poke the wake lock to keep the lockscreen alive
diff --git a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
index 52fb875..5b9160d 100644
--- a/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
+++ b/policy/src/com/android/internal/policy/impl/KeyguardViewMediator.java
@@ -567,6 +567,7 @@
synchronized (KeyguardViewMediator.this) {
if (mHidden != isHidden) {
mHidden = isHidden;
+ updateActivityLockScreenState();
adjustUserActivityLocked();
adjustStatusBarLocked();
}
@@ -1162,6 +1163,14 @@
}
}
+ private void updateActivityLockScreenState() {
+ try {
+ ActivityManagerNative.getDefault().setLockScreenShown(
+ mShowing && !mHidden);
+ } catch (RemoteException e) {
+ }
+ }
+
/**
* Handle message sent by {@link #showLocked}.
* @see #SHOW
@@ -1173,6 +1182,7 @@
mKeyguardViewManager.show();
mShowing = true;
+ updateActivityLockScreenState();
adjustUserActivityLocked();
adjustStatusBarLocked();
try {
@@ -1207,6 +1217,7 @@
mKeyguardViewManager.hide();
mShowing = false;
+ updateActivityLockScreenState();
adjustUserActivityLocked();
adjustStatusBarLocked();
}
@@ -1324,6 +1335,7 @@
if (DEBUG) Log.d(TAG, "handleVerifyUnlock");
mKeyguardViewManager.verifyUnlock();
mShowing = true;
+ updateActivityLockScreenState();
}
}
diff --git a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
index 39ede89..c382646 100644
--- a/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
+++ b/policy/src/com/android/internal/policy/impl/LockPatternKeyguardView.java
@@ -101,15 +101,17 @@
private boolean mShowLockBeforeUnlock = false;
- // The following were added to support FaceLock
- private FaceUnlock mFaceUnlock;
- private final Object mFaceLockStartupLock = new Object();
+ // Interface to a biometric sensor that can optionally be used to unlock the device
+ private BiometricSensorUnlock mBiometricUnlock;
+ private final Object mBiometricUnlockStartupLock = new Object();
+ // Long enough to stay visible while dialer comes up
+ // Short enough to not be visible if the user goes back immediately
+ private final int BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT = 1000;
private boolean mRequiresSim;
- //True if we have some sort of overlay on top of the Lockscreen
- //Also true if we've activated a phone call, either emergency dialing or incoming
- //This resets when the phone is turned off with no current call
- private boolean mHasOverlay;
+ // True if the biometric unlock should not be displayed. For example, if there is an overlay on
+ // lockscreen or the user is plugging in / unplugging the device.
+ private boolean mSupressBiometricUnlock;
//True if a dialog is currently displaying on top of this window
//Unlike other overlays, this does not close with a power button cycle
private boolean mHasDialog = false;
@@ -308,15 +310,15 @@
}
public void takeEmergencyCallAction() {
- mHasOverlay = true;
+ mSupressBiometricUnlock = true;
- // Continue showing FaceLock area until dialer comes up or call is resumed
- if (mFaceUnlock.installedAndSelected() && mFaceUnlock.isServiceRunning()) {
- mFaceUnlock.showAreaWithTimeout(mFaceUnlock.viewAreaEmergencyDialerTimeout());
+ if (mBiometricUnlock.installedAndSelected() && mBiometricUnlock.isRunning()) {
+ // Continue covering backup lock until dialer comes up or call is resumed
+ mBiometricUnlock.show(BIOMETRIC_AREA_EMERGENCY_DIALER_TIMEOUT);
}
- // FaceLock must be stopped if it is running when emergency call is pressed
- mFaceUnlock.stopAndUnbind();
+ // The biometric unlock must be stopped if it is running when emergency call is pressed
+ mBiometricUnlock.stop();
pokeWakelock(EMERGENCY_CALL_TIMEOUT);
if (TelephonyManager.getDefault().getCallState()
@@ -421,7 +423,7 @@
LockPatternUtils lockPatternUtils, KeyguardWindowController controller) {
super(context, callback);
- mFaceUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
+ mBiometricUnlock = new FaceUnlock(context, updateMonitor, lockPatternUtils,
mKeyguardScreenCallback);
mConfiguration = context.getResources().getConfiguration();
mEnableFallback = false;
@@ -429,7 +431,7 @@
mUpdateMonitor = updateMonitor;
mLockPatternUtils = lockPatternUtils;
mWindowController = controller;
- mHasOverlay = false;
+ mSupressBiometricUnlock = false;
mPluggedIn = mUpdateMonitor.isDevicePluggedIn();
mScreenOn = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)).isScreenOn();
@@ -528,8 +530,8 @@
if (DEBUG) Log.d(TAG, "screen off");
mScreenOn = false;
mForgotPattern = false;
- mHasOverlay = mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE ||
- mHasDialog;
+ mSupressBiometricUnlock =
+ mUpdateMonitor.getPhoneState() != TelephonyManager.CALL_STATE_IDLE || mHasDialog;
// Emulate activity life-cycle for both lock and unlock screen.
if (mLockScreen != null) {
@@ -541,25 +543,25 @@
saveWidgetState();
- // When screen is turned off, need to unbind from FaceLock service if using FaceLock
- mFaceUnlock.stopAndUnbind();
+ // The biometric unlock must stop when screen turns off.
+ mBiometricUnlock.stop();
}
@Override
public void onScreenTurnedOn() {
if (DEBUG) Log.d(TAG, "screen on");
- boolean runFaceLock = false;
- //Make sure to start facelock iff the screen is both on and focused
- synchronized(mFaceLockStartupLock) {
+ boolean startBiometricUnlock = false;
+ // Start the biometric unlock if and only if the screen is both on and focused
+ synchronized(mBiometricUnlockStartupLock) {
mScreenOn = true;
- runFaceLock = mWindowFocused;
+ startBiometricUnlock = mWindowFocused;
}
show();
restoreWidgetState();
- if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+ if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
}
private void saveWidgetState() {
@@ -578,25 +580,26 @@
}
}
- /** Unbind from facelock if something covers this window (such as an alarm)
- * bind to facelock if the lockscreen window just came into focus, and the screen is on
+ /**
+ * Stop the biometric unlock if something covers this window (such as an alarm)
+ * Start the biometric unlock if the lockscreen window just came into focus and the screen is on
*/
@Override
public void onWindowFocusChanged (boolean hasWindowFocus) {
if (DEBUG) Log.d(TAG, hasWindowFocus ? "focused" : "unfocused");
- boolean runFaceLock = false;
- //Make sure to start facelock iff the screen is both on and focused
- synchronized(mFaceLockStartupLock) {
- if(mScreenOn && !mWindowFocused) runFaceLock = hasWindowFocus;
+ boolean startBiometricUnlock = false;
+ // Start the biometric unlock if and only if the screen is both on and focused
+ synchronized(mBiometricUnlockStartupLock) {
+ if (mScreenOn && !mWindowFocused) startBiometricUnlock = hasWindowFocus;
mWindowFocused = hasWindowFocus;
}
if (!hasWindowFocus) {
- mHasOverlay = true;
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ mSupressBiometricUnlock = true;
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
} else {
mHasDialog = false;
- if (runFaceLock) mFaceUnlock.activateIfAble(mHasOverlay);
+ if (startBiometricUnlock) mBiometricUnlock.start(mSupressBiometricUnlock);
}
}
@@ -610,14 +613,14 @@
((KeyguardScreen) mUnlockScreen).onResume();
}
- if (mFaceUnlock.installedAndSelected() && !mHasOverlay) {
+ if (mBiometricUnlock.installedAndSelected() && !mSupressBiometricUnlock) {
// Note that show() gets called before the screen turns off to set it up for next time
- // it is turned on. We don't want to set a timeout on the FaceLock area here because it
- // may be gone by the time the screen is turned on again. We set the timeout when the
- // screen turns on instead.
- mFaceUnlock.showArea();
+ // it is turned on. We don't want to set a timeout on the biometric unlock here because
+ // it may be gone by the time the screen is turned on again. We set the timeout when
+ // the screen turns on instead.
+ mBiometricUnlock.show(0);
} else {
- mFaceUnlock.hideArea();
+ mBiometricUnlock.hide();
}
}
@@ -651,9 +654,9 @@
removeCallbacks(mRecreateRunnable);
- // When view is hidden, need to unbind from FaceLock service if we are using FaceLock
+ // When view is hidden, we need to stop the biometric unlock
// e.g., when device becomes unlocked
- mFaceUnlock.stopAndUnbind();
+ mBiometricUnlock.stop();
super.onDetachedFromWindow();
}
@@ -670,16 +673,19 @@
InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() {
- /** When somebody plugs in or unplugs the device, we don't want to display faceunlock */
+ /**
+ * When somebody plugs in or unplugs the device, we don't want to display the biometric
+ * unlock.
+ */
@Override
public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn,
int batteryLevel) {
- mHasOverlay |= mPluggedIn != pluggedIn;
+ mSupressBiometricUnlock |= mPluggedIn != pluggedIn;
mPluggedIn = pluggedIn;
- //If it's already running, don't close it down: the unplug didn't start it
- if (!mFaceUnlock.isServiceRunning()) {
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ // If it's already running, don't close it down: the unplug didn't start it
+ if (!mBiometricUnlock.isRunning()) {
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
}
}
@@ -690,20 +696,20 @@
| (mUpdateMonitor.isClockVisible() ? View.STATUS_BAR_DISABLE_CLOCK : 0));
}
- //We need to stop faceunlock when a phonecall comes in
+ // We need to stop the biometric unlock when a phone call comes in
@Override
public void onPhoneStateChanged(int phoneState) {
if (DEBUG) Log.d(TAG, "phone state: " + phoneState);
if(phoneState == TelephonyManager.CALL_STATE_RINGING) {
- mHasOverlay = true;
- mFaceUnlock.stopAndUnbind();
- mFaceUnlock.hideArea();
+ mSupressBiometricUnlock = true;
+ mBiometricUnlock.stop();
+ mBiometricUnlock.hide();
}
}
@Override
public void onUserChanged(int userId) {
- mFaceUnlock.stopAndUnbind();
+ mBiometricUnlock.stop();
mLockPatternUtils.setCurrentUser(userId);
updateScreen(getInitialMode(), true);
}
@@ -766,7 +772,7 @@
mUnlockScreen = null;
}
mUpdateMonitor.removeCallback(this);
- mFaceUnlock.cleanUp();
+ mBiometricUnlock.cleanUp();
}
private boolean isSecure() {
@@ -816,10 +822,10 @@
final UnlockMode unlockMode = getUnlockMode();
if (mode == Mode.UnlockScreen && unlockMode != UnlockMode.Unknown) {
if (force || mUnlockScreen == null || unlockMode != mUnlockScreenMode) {
- boolean restartFaceLock = mFaceUnlock.stopIfRunning();
+ boolean restartBiometricUnlock = mBiometricUnlock.stop();
recreateUnlockScreen(unlockMode);
- if (restartFaceLock) {
- mFaceUnlock.activateIfAble(mHasOverlay);
+ if (restartBiometricUnlock) {
+ mBiometricUnlock.start(mSupressBiometricUnlock);
}
}
}
@@ -933,7 +939,8 @@
throw new IllegalArgumentException("unknown unlock mode " + unlockMode);
}
initializeTransportControlView(unlockView);
- mFaceUnlock.initializeAreaView(unlockView); // Only shows view if FaceLock is enabled
+ // Only shows view if the biometric unlock is enabled
+ mBiometricUnlock.initializeAreaView(unlockView);
mUnlockScreenMode = unlockMode;
return unlockView;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 1891146..897b8d0 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -909,7 +909,7 @@
mPluggedIn = (0 != intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
}
- mVibrator = new Vibrator();
+ mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mLongPressVibePattern = getLongIntArray(mContext.getResources(),
com.android.internal.R.array.config_longPressVibePattern);
mVirtualKeyVibePattern = getLongIntArray(mContext.getResources(),
@@ -1628,9 +1628,10 @@
return 0;
}
- public Animation createForceHideEnterAnimation() {
- return AnimationUtils.loadAnimation(mContext,
- com.android.internal.R.anim.lock_screen_behind_enter);
+ public Animation createForceHideEnterAnimation(boolean onWallpaper) {
+ return AnimationUtils.loadAnimation(mContext, onWallpaper
+ ? com.android.internal.R.anim.lock_screen_wallpaper_behind_enter
+ : com.android.internal.R.anim.lock_screen_behind_enter);
}
static ITelephony getTelephonyService() {
@@ -2245,12 +2246,11 @@
vf.right = mStableRight;
vf.bottom = mStableBottom;
+ // Let the status bar determine its size.
mStatusBar.computeFrameLw(pf, df, vf, vf);
- final Rect r = mStatusBar.getFrameLw();
- // Compute the stable dimensions whether or not the status bar is hidden.
- if (mDockTop == r.top) mStableTop = r.bottom;
- else if (mDockBottom == r.bottom) mStableBottom = r.top;
+ // For layout, the status bar is always at the top with our fixed height.
+ mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
// If the status bar is hidden, we don't want to cause
// windows behind it to scroll.
@@ -2258,8 +2258,7 @@
// Status bar may go away, so the screen area it occupies
// is available to apps but just covering them when the
// status bar is visible.
- if (mDockTop == r.top) mDockTop = r.bottom;
- else if (mDockBottom == r.bottom) mDockBottom = r.top;
+ mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
mContentTop = mCurTop = mDockTop;
mContentBottom = mCurBottom = mDockBottom;
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index 744f2ad..c0eb1b9 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -117,6 +117,8 @@
}
}
identifier.descriptor = sha1(rawDescriptor);
+ ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.string(),
+ identifier.descriptor.string());
}
// --- Global Functions ---
@@ -159,12 +161,14 @@
const InputDeviceIdentifier& identifier) :
next(NULL),
fd(fd), id(id), path(path), identifier(identifier),
- classes(0), configuration(NULL), virtualKeyMap(NULL) {
+ classes(0), configuration(NULL), virtualKeyMap(NULL),
+ ffEffectPlaying(false), ffEffectId(-1) {
memset(keyBitmask, 0, sizeof(keyBitmask));
memset(absBitmask, 0, sizeof(absBitmask));
memset(relBitmask, 0, sizeof(relBitmask));
memset(swBitmask, 0, sizeof(swBitmask));
memset(ledBitmask, 0, sizeof(ledBitmask));
+ memset(ffBitmask, 0, sizeof(ffBitmask));
memset(propBitmask, 0, sizeof(propBitmask));
}
@@ -434,58 +438,35 @@
return false;
}
-status_t EventHub::mapKey(int32_t deviceId, int scancode,
- int32_t* outKeycode, uint32_t* outFlags) const
-{
+status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t* outKeycode, uint32_t* outFlags) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
-
+
if (device && device->keyMap.haveKeyLayout()) {
- status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags);
+ status_t err = device->keyMap.keyLayoutMap->mapKey(
+ scanCode, usageCode, outKeycode, outFlags);
if (err == NO_ERROR) {
return NO_ERROR;
}
}
-
- if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
- device = getDeviceLocked(mBuiltInKeyboardId);
-
- if (device && device->keyMap.haveKeyLayout()) {
- status_t err = device->keyMap.keyLayoutMap->mapKey(scancode, outKeycode, outFlags);
- if (err == NO_ERROR) {
- return NO_ERROR;
- }
- }
- }
-
+
*outKeycode = 0;
*outFlags = 0;
return NAME_NOT_FOUND;
}
-status_t EventHub::mapAxis(int32_t deviceId, int scancode, AxisInfo* outAxisInfo) const
-{
+status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const {
AutoMutex _l(mLock);
Device* device = getDeviceLocked(deviceId);
if (device && device->keyMap.haveKeyLayout()) {
- status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
+ status_t err = device->keyMap.keyLayoutMap->mapAxis(scanCode, outAxisInfo);
if (err == NO_ERROR) {
return NO_ERROR;
}
}
- if (mBuiltInKeyboardId != NO_BUILT_IN_KEYBOARD) {
- device = getDeviceLocked(mBuiltInKeyboardId);
-
- if (device && device->keyMap.haveKeyLayout()) {
- status_t err = device->keyMap.keyLayoutMap->mapAxis(scancode, outAxisInfo);
- if (err == NO_ERROR) {
- return NO_ERROR;
- }
- }
- }
-
return NAME_NOT_FOUND;
}
@@ -555,6 +536,62 @@
return NULL;
}
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ ff_effect effect;
+ memset(&effect, 0, sizeof(effect));
+ effect.type = FF_RUMBLE;
+ effect.id = device->ffEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999999LL) / 1000000LL;
+ effect.replay.delay = 0;
+ if (ioctl(device->fd, EVIOCSFF, &effect)) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectId = effect.id;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 1;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectPlaying = true;
+ }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ if (device->ffEffectPlaying) {
+ device->ffEffectPlaying = false;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 0;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ }
+ }
+}
+
EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
if (deviceId == BUILT_IN_KEYBOARD_ID) {
deviceId = mBuiltInKeyboardId;
@@ -729,16 +766,8 @@
#endif
event->deviceId = deviceId;
event->type = iev.type;
- event->scanCode = iev.code;
+ event->code = iev.code;
event->value = iev.value;
- event->keyCode = AKEYCODE_UNKNOWN;
- event->flags = 0;
- if (iev.type == EV_KEY && device->keyMap.haveKeyLayout()) {
- status_t err = device->keyMap.keyLayoutMap->mapKey(iev.code,
- &event->keyCode, &event->flags);
- ALOGV("iev.code=%d keyCode=%d flags=0x%08x err=%d\n",
- iev.code, event->keyCode, event->flags, err);
- }
event += 1;
}
capacity -= count;
@@ -749,6 +778,11 @@
break;
}
}
+ } else if (eventItem.events & EPOLLHUP) {
+ ALOGI("Removing device %s due to epoll hang-up event.",
+ device->identifier.name.string());
+ deviceChanged = true;
+ closeDeviceLocked(device);
} else {
ALOGW("Received unexpected epoll event 0x%08x for device %s.",
eventItem.events, device->identifier.name.string());
@@ -960,7 +994,7 @@
ALOGV(" name: \"%s\"\n", identifier.name.string());
ALOGV(" location: \"%s\"\n", identifier.location.string());
ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.string());
- ALOGV(" descriptor: \"%s\" (%s)\n", identifier.descriptor.string(), rawDescriptor.string());
+ ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.string());
ALOGV(" driver: v%d.%d.%d\n",
driverVersion >> 16, (driverVersion >> 8) & 0xff, driverVersion & 0xff);
@@ -973,6 +1007,7 @@
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+ ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
// See if this is a keyboard. Ignore everything in the button range except for
@@ -1034,6 +1069,11 @@
}
}
+ // Check whether this device supports the vibrator.
+ if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+ }
+
// Configure virtual keys.
if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
// Load the virtual keys for the touch screen, if any.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index c35df109..51d2bac 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -39,8 +39,8 @@
/* Convenience constants. */
-#define BTN_FIRST 0x100 // first button scancode
-#define BTN_LAST 0x15f // last button scancode
+#define BTN_FIRST 0x100 // first button code
+#define BTN_LAST 0x15f // last button code
namespace android {
@@ -58,10 +58,8 @@
nsecs_t when;
int32_t deviceId;
int32_t type;
- int32_t scanCode;
- int32_t keyCode;
+ int32_t code;
int32_t value;
- uint32_t flags;
};
/* Describes an absolute axis. */
@@ -115,6 +113,9 @@
/* The input device is a joystick (implies gamepad, has joystick absolute axes). */
INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+ /* The input device has a vibrator (supports FF_RUMBLE). */
+ INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
/* The input device is virtual (not a real device, not part of UI configuration). */
INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
@@ -173,10 +174,10 @@
virtual bool hasInputProperty(int32_t deviceId, int property) const = 0;
- virtual status_t mapKey(int32_t deviceId, int scancode,
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t* outKeycode, uint32_t* outFlags) const = 0;
- virtual status_t mapAxis(int32_t deviceId, int scancode,
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
AxisInfo* outAxisInfo) const = 0;
// Sets devices that are excluded from opening.
@@ -221,6 +222,10 @@
virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+ /* Control the vibrator. */
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
/* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
virtual void requestReopenDevices() = 0;
@@ -252,10 +257,10 @@
virtual bool hasInputProperty(int32_t deviceId, int property) const;
- virtual status_t mapKey(int32_t deviceId, int scancode,
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t* outKeycode, uint32_t* outFlags) const;
- virtual status_t mapAxis(int32_t deviceId, int scancode,
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
AxisInfo* outAxisInfo) const;
virtual void setExcludedDevices(const Vector<String8>& devices);
@@ -279,6 +284,9 @@
virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+ virtual void vibrate(int32_t deviceId, nsecs_t duration);
+ virtual void cancelVibrate(int32_t deviceId);
+
virtual void requestReopenDevices();
virtual void wake();
@@ -305,6 +313,7 @@
uint8_t relBitmask[(REL_MAX + 1) / 8];
uint8_t swBitmask[(SW_MAX + 1) / 8];
uint8_t ledBitmask[(LED_MAX + 1) / 8];
+ uint8_t ffBitmask[(FF_MAX + 1) / 8];
uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
String8 configurationFile;
@@ -312,6 +321,9 @@
VirtualKeyMap* virtualKeyMap;
KeyMap keyMap;
+ bool ffEffectPlaying;
+ int16_t ffEffectId; // initially -1
+
Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
~Device();
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 42512d8..8c37fbb 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,6 +36,9 @@
// Log debug messages about gesture detection.
#define DEBUG_GESTURES 0
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
#include "InputReader.h"
#include <cutils/log.h>
@@ -237,7 +240,8 @@
const sp<InputReaderPolicyInterface>& policy,
const sp<InputListenerInterface>& listener) :
mContext(this), mEventHub(eventHub), mPolicy(policy),
- mGlobalMetaState(0), mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
+ mGlobalMetaState(0), mGeneration(1),
+ mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
mConfigurationChangesToRefresh(0) {
mQueuedListener = new QueuedInputListener(listener);
@@ -257,18 +261,22 @@
}
void InputReader::loopOnce() {
+ int32_t oldGeneration;
int32_t timeoutMillis;
+ bool inputDevicesChanged = false;
+ Vector<InputDeviceInfo> inputDevices;
{ // acquire lock
AutoMutex _l(mLock);
+ oldGeneration = mGeneration;
+ timeoutMillis = -1;
+
uint32_t changes = mConfigurationChangesToRefresh;
if (changes) {
mConfigurationChangesToRefresh = 0;
+ timeoutMillis = 0;
refreshConfigurationLocked(changes);
- }
-
- timeoutMillis = -1;
- if (mNextTimeout != LLONG_MAX) {
+ } else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
@@ -283,7 +291,8 @@
if (count) {
processEventsLocked(mEventBuffer, count);
}
- if (!count || timeoutMillis == 0) {
+
+ if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
if (now >= mNextTimeout) {
#if DEBUG_RAW_EVENTS
@@ -293,8 +302,18 @@
timeoutExpiredLocked(now);
}
}
+
+ if (oldGeneration != mGeneration) {
+ inputDevicesChanged = true;
+ getInputDevicesLocked(inputDevices);
+ }
} // release lock
+ // Send out a message that the describes the changed input devices.
+ if (inputDevicesChanged) {
+ mPolicy->notifyInputDevicesChanged(inputDevices);
+ }
+
// Flush queued events out to the listener.
// This must happen outside of the lock because the listener could potentially call
// back into the InputReader's methods, such as getScanCodeState, or become blocked
@@ -344,6 +363,12 @@
}
void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
+ return;
+ }
+
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
@@ -359,27 +384,22 @@
identifier.name.string(), device->getSources());
}
- ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
- if (deviceIndex < 0) {
- mDevices.add(deviceId, device);
- } else {
- ALOGW("Ignoring spurious device added event for deviceId %d.", deviceId);
- delete device;
- return;
- }
+ mDevices.add(deviceId, device);
+ bumpGenerationLocked();
}
void InputReader::removeDeviceLocked(nsecs_t when, int32_t deviceId) {
InputDevice* device = NULL;
ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
- if (deviceIndex >= 0) {
- device = mDevices.valueAt(deviceIndex);
- mDevices.removeItemsAt(deviceIndex, 1);
- } else {
+ if (deviceIndex < 0) {
ALOGW("Ignoring spurious device removed event for deviceId %d.", deviceId);
return;
}
+ device = mDevices.valueAt(deviceIndex);
+ mDevices.removeItemsAt(deviceIndex, 1);
+ bumpGenerationLocked();
+
if (device->isIgnored()) {
ALOGI("Device removed: id=%d, name='%s' (ignored non-input device)",
device->getId(), device->getName().string());
@@ -394,7 +414,8 @@
InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
const InputDeviceIdentifier& identifier, uint32_t classes) {
- InputDevice* device = new InputDevice(&mContext, deviceId, identifier, classes);
+ InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
+ identifier, classes);
// External devices.
if (classes & INPUT_DEVICE_CLASS_EXTERNAL) {
@@ -406,6 +427,11 @@
device->addMapper(new SwitchInputMapper(device));
}
+ // Vibrator-like devices.
+ if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+ device->addMapper(new VibratorInputMapper(device));
+ }
+
// Keyboard-like devices.
uint32_t keyboardSource = 0;
int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
@@ -574,42 +600,34 @@
void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
if (when < mNextTimeout) {
mNextTimeout = when;
+ mEventHub->wake();
}
}
+int32_t InputReader::bumpGenerationLocked() {
+ return ++mGeneration;
+}
+
void InputReader::getInputConfiguration(InputConfiguration* outConfiguration) {
AutoMutex _l(mLock);
*outConfiguration = mInputConfiguration;
}
-status_t InputReader::getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) {
+void InputReader::getInputDevices(Vector<InputDeviceInfo>& outInputDevices) {
AutoMutex _l(mLock);
-
- ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
- if (deviceIndex < 0) {
- return NAME_NOT_FOUND;
- }
-
- InputDevice* device = mDevices.valueAt(deviceIndex);
- if (device->isIgnored()) {
- return NAME_NOT_FOUND;
- }
-
- device->getDeviceInfo(outDeviceInfo);
- return OK;
+ getInputDevicesLocked(outInputDevices);
}
-void InputReader::getInputDeviceIds(Vector<int32_t>& outDeviceIds) {
- AutoMutex _l(mLock);
-
- outDeviceIds.clear();
+void InputReader::getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices) {
+ outInputDevices.clear();
size_t numDevices = mDevices.size();
for (size_t i = 0; i < numDevices; i++) {
InputDevice* device = mDevices.valueAt(i);
if (!device->isIgnored()) {
- outDeviceIds.add(device->getId());
+ outInputDevices.push();
+ device->getDeviceInfo(&outInputDevices.editTop());
}
}
}
@@ -710,6 +728,27 @@
}
}
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->cancelVibrate(token);
+ }
+}
+
void InputReader::dump(String8& dump) {
AutoMutex _l(mLock);
@@ -824,6 +863,11 @@
mReader->requestTimeoutAtTimeLocked(when);
}
+int32_t InputReader::ContextImpl::bumpGeneration() {
+ // lock is already held by the input loop
+ return mReader->bumpGenerationLocked();
+}
+
InputReaderPolicyInterface* InputReader::ContextImpl::getPolicy() {
return mReader->mPolicy.get();
}
@@ -854,9 +898,10 @@
// --- InputDevice ---
-InputDevice::InputDevice(InputReaderContext* context, int32_t id,
+InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
const InputDeviceIdentifier& identifier, uint32_t classes) :
- mContext(context), mId(id), mIdentifier(identifier), mClasses(classes),
+ mContext(context), mId(id), mGeneration(generation),
+ mIdentifier(identifier), mClasses(classes),
mSources(0), mIsExternal(false), mDropUntilNextSync(false) {
}
@@ -874,6 +919,7 @@
dump.appendFormat(INDENT "Device %d: %s\n", deviceInfo.getId(),
deviceInfo.getName().string());
+ dump.appendFormat(INDENT2 "Generation: %d\n", mGeneration);
dump.appendFormat(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
@@ -946,14 +992,12 @@
size_t numMappers = mMappers.size();
for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
#if DEBUG_RAW_EVENTS
- ALOGD("Input event: device=%d type=0x%04x scancode=0x%04x "
- "keycode=0x%04x value=0x%08x flags=0x%08x",
- rawEvent->deviceId, rawEvent->type, rawEvent->scanCode, rawEvent->keyCode,
- rawEvent->value, rawEvent->flags);
+ ALOGD("Input event: device=%d type=0x%04x code=0x%04x value=0x%08x",
+ rawEvent->deviceId, rawEvent->type, rawEvent->code, rawEvent->value);
#endif
if (mDropUntilNextSync) {
- if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
mDropUntilNextSync = false;
#if DEBUG_RAW_EVENTS
ALOGD("Recovered from input event buffer overrun.");
@@ -963,7 +1007,7 @@
ALOGD("Dropped input event while waiting for next input sync.");
#endif
}
- } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_DROPPED) {
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
ALOGI("Detected input event buffer overrun for device %s.", getName().string());
mDropUntilNextSync = true;
reset(rawEvent->when);
@@ -985,7 +1029,7 @@
}
void InputDevice::getDeviceInfo(InputDeviceInfo* outDeviceInfo) {
- outDeviceInfo->initialize(mId, mIdentifier.name, mIdentifier.descriptor);
+ outDeviceInfo->initialize(mId, mGeneration, mIdentifier.name, mIdentifier.descriptor);
size_t numMappers = mMappers.size();
for (size_t i = 0; i < numMappers; i++) {
@@ -1038,6 +1082,23 @@
return result;
}
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->cancelVibrate(token);
+ }
+}
+
int32_t InputDevice::getMetaState() {
int32_t result = 0;
size_t numMappers = mMappers.size();
@@ -1056,6 +1117,10 @@
}
}
+void InputDevice::bumpGeneration() {
+ mGeneration = mContext->bumpGeneration();
+}
+
void InputDevice::notifyReset(nsecs_t when) {
NotifyDeviceResetArgs args(when, mId);
mContext->getListener()->notifyDeviceReset(&args);
@@ -1092,7 +1157,7 @@
void CursorButtonAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_KEY) {
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case BTN_LEFT:
mBtnLeft = rawEvent->value;
break;
@@ -1159,7 +1224,7 @@
void CursorMotionAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_REL) {
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case REL_X:
mRelX = rawEvent->value;
break;
@@ -1198,7 +1263,7 @@
void CursorScrollAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_REL) {
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case REL_WHEEL:
mRelWheel = rawEvent->value;
break;
@@ -1261,7 +1326,7 @@
void TouchButtonAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_KEY) {
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case BTN_TOUCH:
mBtnTouch = rawEvent->value;
break;
@@ -1467,7 +1532,7 @@
void SingleTouchMotionAccumulator::process(const RawEvent* rawEvent) {
if (rawEvent->type == EV_ABS) {
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case ABS_X:
mAbsX = rawEvent->value;
break;
@@ -1551,7 +1616,7 @@
if (rawEvent->type == EV_ABS) {
bool newSlot = false;
if (mUsingSlotsProtocol) {
- if (rawEvent->scanCode == ABS_MT_SLOT) {
+ if (rawEvent->code == ABS_MT_SLOT) {
mCurrentSlot = rawEvent->value;
newSlot = true;
}
@@ -1570,7 +1635,7 @@
} else {
Slot* slot = &mSlots[mCurrentSlot];
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case ABS_MT_POSITION_X:
slot->mInUse = true;
slot->mAbsMTPositionX = rawEvent->value;
@@ -1626,7 +1691,7 @@
break;
}
}
- } else if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_MT_REPORT) {
+ } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) {
// MultiTouch Sync: The driver has returned all data for *one* of the pointers.
mCurrentSlot += 1;
}
@@ -1719,6 +1784,13 @@
return false;
}
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+}
+
+void InputMapper::cancelVibrate(int32_t token) {
+}
+
int32_t InputMapper::getMetaState() {
return 0;
}
@@ -1730,6 +1802,10 @@
return getEventHub()->getAbsoluteAxisInfo(getDeviceId(), axis, axisInfo);
}
+void InputMapper::bumpGeneration() {
+ mDevice->bumpGeneration();
+}
+
void InputMapper::dumpRawAbsoluteAxisInfo(String8& dump,
const RawAbsoluteAxisInfo& axis, const char* name) {
if (axis.valid) {
@@ -1757,7 +1833,7 @@
void SwitchInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_SW:
- processSwitch(rawEvent->when, rawEvent->scanCode, rawEvent->value);
+ processSwitch(rawEvent->when, rawEvent->code, rawEvent->value);
break;
}
}
@@ -1772,6 +1848,120 @@
}
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device) :
+ InputMapper(device), mVibrating(false) {
+}
+
+VibratorInputMapper::~VibratorInputMapper() {
+}
+
+uint32_t VibratorInputMapper::getSources() {
+ return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+ // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+#if DEBUG_VIBRATOR
+ String8 patternStr;
+ for (size_t i = 0; i < patternSize; i++) {
+ if (i != 0) {
+ patternStr.append(", ");
+ }
+ patternStr.appendFormat("%lld", pattern[i]);
+ }
+ ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d",
+ getDeviceId(), patternStr.string(), repeat, token);
+#endif
+
+ mVibrating = true;
+ memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+ mPatternSize = patternSize;
+ mRepeat = repeat;
+ mToken = token;
+ mIndex = -1;
+
+ nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+ ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+ if (mVibrating && mToken == token) {
+ stopVibrating();
+ }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ if (mVibrating) {
+ if (when >= mNextStepTime) {
+ nextStep();
+ } else {
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+ }
+ }
+}
+
+void VibratorInputMapper::nextStep() {
+ mIndex += 1;
+ if (size_t(mIndex) >= mPatternSize) {
+ if (mRepeat < 0) {
+ // We are done.
+ stopVibrating();
+ return;
+ }
+ mIndex = mRepeat;
+ }
+
+ bool vibratorOn = mIndex & 1;
+ nsecs_t duration = mPattern[mIndex];
+ if (vibratorOn) {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld",
+ getDeviceId(), duration);
+#endif
+ getEventHub()->vibrate(getDeviceId(), duration);
+ } else {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+ }
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNextStepTime = now + duration;
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+ mVibrating = false;
+#if DEBUG_VIBRATOR
+ ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Vibrator Input Mapper:\n");
+ dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+
// --- KeyboardInputMapper ---
KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
@@ -1849,6 +2039,7 @@
mMetaState = AMETA_NONE;
mDownTime = 0;
mKeyDowns.clear();
+ mCurrentHidUsage = 0;
resetLedState();
@@ -1858,13 +2049,32 @@
void KeyboardInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_KEY: {
- int32_t scanCode = rawEvent->scanCode;
+ int32_t scanCode = rawEvent->code;
+ int32_t usageCode = mCurrentHidUsage;
+ mCurrentHidUsage = 0;
+
if (isKeyboardOrGamepadKey(scanCode)) {
- processKey(rawEvent->when, rawEvent->value != 0, rawEvent->keyCode, scanCode,
- rawEvent->flags);
+ int32_t keyCode;
+ uint32_t flags;
+ if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
+ keyCode = AKEYCODE_UNKNOWN;
+ flags = 0;
+ }
+ processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
}
break;
}
+ case EV_MSC: {
+ if (rawEvent->code == MSC_SCAN) {
+ mCurrentHidUsage = rawEvent->value;
+ }
+ break;
+ }
+ case EV_SYN: {
+ if (rawEvent->code == SYN_REPORT) {
+ mCurrentHidUsage = 0;
+ }
+ }
}
}
@@ -2119,6 +2329,7 @@
} else {
mOrientation = DISPLAY_ORIENTATION_0;
}
+ bumpGeneration();
}
}
@@ -2183,7 +2394,7 @@
mCursorMotionAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
- if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
}
}
@@ -2980,6 +3191,7 @@
// Inform the dispatcher about the changes.
*outResetNeeded = true;
+ bumpGeneration();
}
}
@@ -3016,8 +3228,7 @@
virtualKey.scanCode = virtualKeyDefinition.scanCode;
int32_t keyCode;
uint32_t flags;
- if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode,
- & keyCode, & flags)) {
+ if (getEventHub()->mapKey(getDeviceId(), virtualKey.scanCode, 0, &keyCode, &flags)) {
ALOGW(INDENT "VirtualKey %d: could not obtain key code, ignoring",
virtualKey.scanCode);
mVirtualKeys.pop(); // drop the key
@@ -3311,7 +3522,7 @@
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(rawEvent);
- if (rawEvent->type == EV_SYN && rawEvent->scanCode == SYN_REPORT) {
+ if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
sync(rawEvent->when);
}
}
@@ -5920,7 +6131,7 @@
void JoystickInputMapper::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EV_ABS: {
- ssize_t index = mAxes.indexOfKey(rawEvent->scanCode);
+ ssize_t index = mAxes.indexOfKey(rawEvent->code);
if (index >= 0) {
Axis& axis = mAxes.editValueAt(index);
float newValue, highNewValue;
@@ -5956,7 +6167,7 @@
}
case EV_SYN:
- switch (rawEvent->scanCode) {
+ switch (rawEvent->code) {
case SYN_REPORT:
sync(rawEvent->when, false /*force*/);
break;
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 8520a75..ed57596 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -33,6 +33,14 @@
#include <stddef.h>
#include <unistd.h>
+// Maximum supported size of a vibration pattern.
+// Must be at least 2.
+#define MAX_VIBRATE_PATTERN_SIZE 100
+
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL)
+
namespace android {
class InputDevice;
@@ -209,6 +217,11 @@
/* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+ /* Notifies the input reader policy that some input devices have changed
+ * and provides information about all current input devices.
+ */
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) = 0;
};
@@ -240,16 +253,11 @@
*/
virtual void getInputConfiguration(InputConfiguration* outConfiguration) = 0;
- /* Gets information about the specified input device.
- * Returns OK if the device information was obtained or NAME_NOT_FOUND if there
- * was no such device.
+ /* Gets information about all input devices.
*
* This method may be called on any thread (usually by the input manager).
*/
- virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo) = 0;
-
- /* Gets the list of all registered device ids. */
- virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds) = 0;
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices) = 0;
/* Query current input state. */
virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
@@ -267,6 +275,11 @@
* The changes flag is a bitfield that indicates what has changed and whether
* the input devices must all be reopened. */
virtual void requestRefreshConfiguration(uint32_t changes) = 0;
+
+ /* Controls the vibrator of a particular input device. */
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) = 0;
+ virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
};
@@ -288,6 +301,7 @@
virtual void fadePointer() = 0;
virtual void requestTimeoutAtTime(nsecs_t when) = 0;
+ virtual int32_t bumpGeneration() = 0;
virtual InputReaderPolicyInterface* getPolicy() = 0;
virtual InputListenerInterface* getListener() = 0;
@@ -319,9 +333,7 @@
virtual void loopOnce();
virtual void getInputConfiguration(InputConfiguration* outConfiguration);
-
- virtual status_t getInputDeviceInfo(int32_t deviceId, InputDeviceInfo* outDeviceInfo);
- virtual void getInputDeviceIds(Vector<int32_t>& outDeviceIds);
+ virtual void getInputDevices(Vector<InputDeviceInfo>& outInputDevices);
virtual int32_t getScanCodeState(int32_t deviceId, uint32_t sourceMask,
int32_t scanCode);
@@ -335,6 +347,10 @@
virtual void requestRefreshConfiguration(uint32_t changes);
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token);
+ virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual InputDevice* createDeviceLocked(int32_t deviceId,
@@ -353,6 +369,7 @@
InputDevice* device, int32_t keyCode, int32_t scanCode);
virtual void fadePointer();
virtual void requestTimeoutAtTime(nsecs_t when);
+ virtual int32_t bumpGeneration();
virtual InputReaderPolicyInterface* getPolicy();
virtual InputListenerInterface* getListener();
virtual EventHubInterface* getEventHub();
@@ -393,9 +410,14 @@
void fadePointerLocked();
+ int32_t mGeneration;
+ int32_t bumpGenerationLocked();
+
InputConfiguration mInputConfiguration;
void updateInputConfigurationLocked();
+ void getInputDevicesLocked(Vector<InputDeviceInfo>& outInputDevices);
+
nsecs_t mDisableVirtualKeysTimeout;
void disableVirtualKeysUntilLocked(nsecs_t time);
bool shouldDropVirtualKeyLocked(nsecs_t now,
@@ -432,12 +454,13 @@
/* Represents the state of a single input device. */
class InputDevice {
public:
- InputDevice(InputReaderContext* context, int32_t id,
+ InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
const InputDeviceIdentifier& identifier, uint32_t classes);
~InputDevice();
inline InputReaderContext* getContext() { return mContext; }
inline int32_t getId() { return mId; }
+ inline int32_t getGeneration() { return mGeneration; }
inline const String8& getName() { return mIdentifier.name; }
inline uint32_t getClasses() { return mClasses; }
inline uint32_t getSources() { return mSources; }
@@ -460,11 +483,15 @@
int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
+ void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+ void cancelVibrate(int32_t token);
int32_t getMetaState();
void fadePointer();
+ void bumpGeneration();
+
void notifyReset(nsecs_t when);
inline const PropertyMap& getConfiguration() { return mConfiguration; }
@@ -487,6 +514,7 @@
private:
InputReaderContext* mContext;
int32_t mId;
+ int32_t mGeneration;
InputDeviceIdentifier mIdentifier;
uint32_t mClasses;
@@ -839,6 +867,9 @@
virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
virtual int32_t getMetaState();
@@ -849,6 +880,7 @@
InputReaderContext* mContext;
status_t getAbsoluteAxisInfo(int32_t axis, RawAbsoluteAxisInfo* axisInfo);
+ void bumpGeneration();
static void dumpRawAbsoluteAxisInfo(String8& dump,
const RawAbsoluteAxisInfo& axis, const char* name);
@@ -870,6 +902,35 @@
};
+class VibratorInputMapper : public InputMapper {
+public:
+ VibratorInputMapper(InputDevice* device);
+ virtual ~VibratorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+ virtual void timeoutExpired(nsecs_t when);
+ virtual void dump(String8& dump);
+
+private:
+ bool mVibrating;
+ nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+ size_t mPatternSize;
+ ssize_t mRepeat;
+ int32_t mToken;
+ ssize_t mIndex;
+ nsecs_t mNextStepTime;
+
+ void nextStep();
+ void stopVibrating();
+};
+
+
class KeyboardInputMapper : public InputMapper {
public:
KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
@@ -904,6 +965,8 @@
int32_t mMetaState;
nsecs_t mDownTime; // time of most recent key down
+ int32_t mCurrentHidUsage; // most recent HID usage seen this packet, or 0 if none
+
struct LedState {
bool avail; // led is available
bool on; // we think the led is currently on
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 057ad18..94d4189 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -127,6 +127,7 @@
class FakeInputReaderPolicy : public InputReaderPolicyInterface {
InputReaderConfiguration mConfig;
KeyedVector<int32_t, sp<FakePointerController> > mPointerControllers;
+ Vector<InputDeviceInfo> mInputDevices;
protected:
virtual ~FakeInputReaderPolicy() { }
@@ -141,10 +142,6 @@
mConfig.setDisplayInfo(displayId, true /*external*/, width, height, orientation);
}
- virtual nsecs_t getVirtualKeyQuietTime() {
- return 0;
- }
-
void addExcludedDeviceName(const String8& deviceName) {
mConfig.excludedDeviceNames.push(deviceName);
}
@@ -157,6 +154,10 @@
return &mConfig;
}
+ const Vector<InputDeviceInfo>& getInputDevices() const {
+ return mInputDevices;
+ }
+
private:
virtual void getReaderConfiguration(InputReaderConfiguration* outConfig) {
*outConfig = mConfig;
@@ -165,6 +166,10 @@
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
return mPointerControllers.valueFor(deviceId);
}
+
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+ mInputDevices = inputDevices;
+ }
};
@@ -283,7 +288,8 @@
KeyedVector<int32_t, int32_t> scanCodeStates;
KeyedVector<int32_t, int32_t> switchStates;
KeyedVector<int32_t, int32_t> absoluteAxisValue;
- KeyedVector<int32_t, KeyInfo> keys;
+ KeyedVector<int32_t, KeyInfo> keysByScanCode;
+ KeyedVector<int32_t, KeyInfo> keysByUsageCode;
KeyedVector<int32_t, bool> leds;
Vector<VirtualKeyDefinition> virtualKeys;
@@ -311,18 +317,18 @@
device->identifier.name = name;
mDevices.add(deviceId, device);
- enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0, 0, 0);
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
}
void removeDevice(int32_t deviceId) {
delete mDevices.valueFor(deviceId);
mDevices.removeItem(deviceId);
- enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0, 0, 0);
+ enqueueEvent(ARBITRARY_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
}
void finishDeviceScan() {
- enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0, 0, 0);
+ enqueueEvent(ARBITRARY_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
}
void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
@@ -374,12 +380,18 @@
device->absoluteAxisValue.replaceValueFor(axis, value);
}
- void addKey(int32_t deviceId, int32_t scanCode, int32_t keyCode, uint32_t flags) {
+ void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
+ int32_t keyCode, uint32_t flags) {
Device* device = getDevice(deviceId);
KeyInfo info;
info.keyCode = keyCode;
info.flags = flags;
- device->keys.add(scanCode, info);
+ if (scanCode) {
+ device->keysByScanCode.add(scanCode, info);
+ }
+ if (usageCode) {
+ device->keysByUsageCode.add(usageCode, info);
+ }
}
void addLed(int32_t deviceId, int32_t led, bool initialState) {
@@ -402,19 +414,17 @@
}
void enqueueEvent(nsecs_t when, int32_t deviceId, int32_t type,
- int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+ int32_t code, int32_t value) {
RawEvent event;
event.when = when;
event.deviceId = deviceId;
event.type = type;
- event.scanCode = scanCode;
- event.keyCode = keyCode;
+ event.code = code;
event.value = value;
- event.flags = flags;
mEvents.push_back(event);
if (type == EV_ABS) {
- setAbsoluteAxisValue(deviceId, scanCode, value);
+ setAbsoluteAxisValue(deviceId, code, value);
}
}
@@ -471,17 +481,17 @@
return false;
}
- virtual status_t mapKey(int32_t deviceId, int scancode,
+ virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t* outKeycode, uint32_t* outFlags) const {
Device* device = getDevice(deviceId);
if (device) {
- ssize_t index = device->keys.indexOfKey(scancode);
- if (index >= 0) {
+ const KeyInfo* key = getKey(device, scanCode, usageCode);
+ if (key) {
if (outKeycode) {
- *outKeycode = device->keys.valueAt(index).keyCode;
+ *outKeycode = key->keyCode;
}
if (outFlags) {
- *outFlags = device->keys.valueAt(index).flags;
+ *outFlags = key->flags;
}
return OK;
}
@@ -489,7 +499,23 @@
return NAME_NOT_FOUND;
}
- virtual status_t mapAxis(int32_t deviceId, int scancode,
+ const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const {
+ if (usageCode) {
+ ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
+ if (index >= 0) {
+ return &device->keysByUsageCode.valueAt(index);
+ }
+ }
+ if (scanCode) {
+ ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
+ if (index >= 0) {
+ return &device->keysByScanCode.valueAt(index);
+ }
+ }
+ return NULL;
+ }
+
+ virtual status_t mapAxis(int32_t deviceId, int32_t scanCode,
AxisInfo* outAxisInfo) const {
return NAME_NOT_FOUND;
}
@@ -561,8 +587,14 @@
Device* device = getDevice(deviceId);
if (device) {
for (size_t i = 0; i < numCodes; i++) {
- for (size_t j = 0; j < device->keys.size(); j++) {
- if (keyCodes[i] == device->keys.valueAt(j).keyCode) {
+ for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
+ if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
+ outFlags[i] = 1;
+ result = true;
+ }
+ }
+ for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
+ if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
outFlags[i] = 1;
result = true;
}
@@ -575,7 +607,7 @@
virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const {
Device* device = getDevice(deviceId);
if (device) {
- ssize_t index = device->keys.indexOfKey(scanCode);
+ ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
return index >= 0;
}
return false;
@@ -614,6 +646,12 @@
return NULL;
}
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) {
+ }
+
+ virtual void cancelVibrate(int32_t deviceId) {
+ }
+
virtual bool isExternal(int32_t deviceId) const {
return false;
}
@@ -640,6 +678,7 @@
sp<InputListenerInterface> mListener;
int32_t mGlobalMetaState;
bool mUpdateGlobalMetaStateWasCalled;
+ int32_t mGeneration;
public:
FakeInputReaderContext(const sp<EventHubInterface>& eventHub,
@@ -695,6 +734,10 @@
virtual void requestTimeoutAtTime(nsecs_t when) {
}
+
+ virtual int32_t bumpGeneration() {
+ return ++mGeneration;
+ }
};
@@ -860,7 +903,8 @@
InputDevice* newDevice(int32_t deviceId, const String8& name, uint32_t classes) {
InputDeviceIdentifier identifier;
identifier.name = name;
- return new InputDevice(&mContext, deviceId, identifier, classes);
+ int32_t generation = deviceId + 1;
+ return new InputDevice(&mContext, deviceId, generation, identifier, classes);
}
protected:
@@ -1018,52 +1062,30 @@
ASSERT_EQ(InputConfiguration::TOUCHSCREEN_NOTOUCH, config.touchScreen);
}
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsValid) {
+TEST_F(InputReaderTest, GetInputDevices) {
ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
INPUT_DEVICE_CLASS_KEYBOARD, NULL));
+ ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("ignored"),
+ 0, NULL)); // no classes so device will be ignored
- InputDeviceInfo info;
- status_t result = mReader->getInputDeviceInfo(1, &info);
+ Vector<InputDeviceInfo> inputDevices;
+ mReader->getInputDevices(inputDevices);
- ASSERT_EQ(OK, result);
- ASSERT_EQ(1, info.getId());
- ASSERT_STREQ("keyboard", info.getName().string());
- ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, info.getKeyboardType());
- ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, info.getSources());
- ASSERT_EQ(size_t(0), info.getMotionRanges().size());
-}
+ ASSERT_EQ(1U, inputDevices.size());
+ ASSERT_EQ(1, inputDevices[0].getId());
+ ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+ ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsInvalid) {
- InputDeviceInfo info;
- status_t result = mReader->getInputDeviceInfo(-1, &info);
-
- ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceInfo_WhenDeviceIdIsIgnored) {
- addDevice(1, String8("ignored"), 0, NULL); // no classes so device will be ignored
-
- InputDeviceInfo info;
- status_t result = mReader->getInputDeviceInfo(1, &info);
-
- ASSERT_EQ(NAME_NOT_FOUND, result);
-}
-
-TEST_F(InputReaderTest, GetInputDeviceIds) {
- sp<FakePointerController> controller = new FakePointerController();
- mFakePolicy->setPointerController(2, controller);
-
- ASSERT_NO_FATAL_FAILURE(addDevice(1, String8("keyboard"),
- INPUT_DEVICE_CLASS_KEYBOARD | INPUT_DEVICE_CLASS_ALPHAKEY, NULL));
- ASSERT_NO_FATAL_FAILURE(addDevice(2, String8("mouse"),
- INPUT_DEVICE_CLASS_CURSOR, NULL));
-
- Vector<int32_t> ids;
- mReader->getInputDeviceIds(ids);
-
- ASSERT_EQ(size_t(2), ids.size());
- ASSERT_EQ(1, ids[0]);
- ASSERT_EQ(2, ids[1]);
+ // Should also have received a notification describing the new input devices.
+ inputDevices = mFakePolicy->getInputDevices();
+ ASSERT_EQ(1U, inputDevices.size());
+ ASSERT_EQ(1, inputDevices[0].getId());
+ ASSERT_STREQ("keyboard", inputDevices[0].getName().string());
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
+ ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
}
TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
@@ -1196,7 +1218,7 @@
ASSERT_NO_FATAL_FAILURE(mapper = addDeviceWithFakeInputMapper(1, String8("fake"),
INPUT_DEVICE_CLASS_KEYBOARD, AINPUT_SOURCE_KEYBOARD, NULL));
- mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, AKEYCODE_A, 1, POLICY_FLAG_WAKE);
+ mFakeEventHub->enqueueEvent(0, 1, EV_KEY, KEY_A, 1);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
@@ -1205,10 +1227,8 @@
ASSERT_EQ(0, event.when);
ASSERT_EQ(1, event.deviceId);
ASSERT_EQ(EV_KEY, event.type);
- ASSERT_EQ(KEY_A, event.scanCode);
- ASSERT_EQ(AKEYCODE_A, event.keyCode);
+ ASSERT_EQ(KEY_A, event.code);
ASSERT_EQ(1, event.value);
- ASSERT_EQ(POLICY_FLAG_WAKE, event.flags);
}
@@ -1218,6 +1238,7 @@
protected:
static const char* DEVICE_NAME;
static const int32_t DEVICE_ID;
+ static const int32_t DEVICE_GENERATION;
static const uint32_t DEVICE_CLASSES;
sp<FakeEventHub> mFakeEventHub;
@@ -1236,7 +1257,8 @@
mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
- mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+ identifier, DEVICE_CLASSES);
}
virtual void TearDown() {
@@ -1251,6 +1273,7 @@
const char* InputDeviceTest::DEVICE_NAME = "device";
const int32_t InputDeviceTest::DEVICE_ID = 1;
+const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
const uint32_t InputDeviceTest::DEVICE_CLASSES = INPUT_DEVICE_CLASS_KEYBOARD
| INPUT_DEVICE_CLASS_TOUCH | INPUT_DEVICE_CLASS_JOYSTICK;
@@ -1403,6 +1426,7 @@
protected:
static const char* DEVICE_NAME;
static const int32_t DEVICE_ID;
+ static const int32_t DEVICE_GENERATION;
static const uint32_t DEVICE_CLASSES;
sp<FakeEventHub> mFakeEventHub;
@@ -1418,7 +1442,8 @@
mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener);
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
- mDevice = new InputDevice(mFakeContext, DEVICE_ID, identifier, DEVICE_CLASSES);
+ mDevice = new InputDevice(mFakeContext, DEVICE_ID, DEVICE_GENERATION,
+ identifier, DEVICE_CLASSES);
mFakeEventHub->addDevice(DEVICE_ID, String8(DEVICE_NAME), 0);
}
@@ -1449,15 +1474,13 @@
}
static void process(InputMapper* mapper, nsecs_t when, int32_t deviceId, int32_t type,
- int32_t scanCode, int32_t keyCode, int32_t value, uint32_t flags) {
+ int32_t code, int32_t value) {
RawEvent event;
event.when = when;
event.deviceId = deviceId;
event.type = type;
- event.scanCode = scanCode;
- event.keyCode = keyCode;
+ event.code = code;
event.value = value;
- event.flags = flags;
mapper->process(&event);
}
@@ -1499,6 +1522,7 @@
const char* InputMapperTest::DEVICE_NAME = "device";
const int32_t InputMapperTest::DEVICE_ID = 1;
+const int32_t InputMapperTest::DEVICE_GENERATION = 2;
const uint32_t InputMapperTest::DEVICE_CLASSES = 0; // not needed for current tests
@@ -1530,7 +1554,7 @@
SwitchInputMapper* mapper = new SwitchInputMapper(mDevice);
addMapperAndConfigure(mapper);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 0, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SW, SW_LID, 1);
NotifySwitchArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args));
@@ -1553,13 +1577,13 @@
int32_t originalScanCode, int32_t originalKeyCode, int32_t rotatedKeyCode) {
NotifyKeyArgs args;
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
ASSERT_EQ(originalScanCode, args.scanCode);
ASSERT_EQ(rotatedKeyCode, args.keyCode);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, originalKeyCode, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, originalScanCode, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
ASSERT_EQ(originalScanCode, args.scanCode);
@@ -1576,13 +1600,18 @@
}
TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
+ const int32_t USAGE_A = 0x070004;
+ const int32_t USAGE_UNKNOWN = 0x07ffff;
+ mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
addMapperAndConfigure(mapper);
- // Key down.
+ // Key down by scan code.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_HOME, AKEYCODE_HOME, 1, POLICY_FLAG_WAKE);
+ EV_KEY, KEY_HOME, 1);
NotifyKeyArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -1596,9 +1625,9 @@
ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
ASSERT_EQ(ARBITRARY_TIME, args.downTime);
- // Key up.
+ // Key up by scan code.
process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
- EV_KEY, KEY_HOME, AKEYCODE_HOME, 0, POLICY_FLAG_WAKE);
+ EV_KEY, KEY_HOME, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(DEVICE_ID, args.deviceId);
ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
@@ -1610,9 +1639,80 @@
ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key down by usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_A);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, 0, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(AKEYCODE_A, args.keyCode);
+ ASSERT_EQ(0, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up by usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_A);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, 0, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_A, args.keyCode);
+ ASSERT_EQ(0, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key down with unknown scan code or usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_KEY, KEY_UNKNOWN, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
+ ASSERT_EQ(0, args.keyCode);
+ ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(0U, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
+
+ // Key up with unknown scan code or usage code.
+ process(mapper, ARBITRARY_TIME, DEVICE_ID,
+ EV_MSC, MSC_SCAN, USAGE_UNKNOWN);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
+ EV_KEY, KEY_UNKNOWN, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(DEVICE_ID, args.deviceId);
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, args.source);
+ ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(0, args.keyCode);
+ ASSERT_EQ(KEY_UNKNOWN, args.scanCode);
+ ASSERT_EQ(AMETA_NONE, args.metaState);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+ ASSERT_EQ(0U, args.policyFlags);
+ ASSERT_EQ(ARBITRARY_TIME, args.downTime);
}
TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFTSHIFT, 0, AKEYCODE_SHIFT_LEFT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
+
KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
addMapperAndConfigure(mapper);
@@ -1622,7 +1722,7 @@
// Metakey down.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 1, 0);
+ EV_KEY, KEY_LEFTSHIFT, 1);
NotifyKeyArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
@@ -1631,21 +1731,21 @@
// Key down.
process(mapper, ARBITRARY_TIME + 1, DEVICE_ID,
- EV_KEY, KEY_A, AKEYCODE_A, 1, 0);
+ EV_KEY, KEY_A, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
// Key up.
process(mapper, ARBITRARY_TIME + 2, DEVICE_ID,
- EV_KEY, KEY_A, AKEYCODE_A, 0, 0);
+ EV_KEY, KEY_A, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, args.metaState);
ASSERT_EQ(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON, mapper->getMetaState());
// Metakey up.
process(mapper, ARBITRARY_TIME + 3, DEVICE_ID,
- EV_KEY, KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT, 0, 0);
+ EV_KEY, KEY_LEFTSHIFT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AMETA_NONE, args.metaState);
ASSERT_EQ(AMETA_NONE, mapper->getMetaState());
@@ -1653,6 +1753,11 @@
}
TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateDPad) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
addMapperAndConfigure(mapper);
@@ -1671,6 +1776,11 @@
}
TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
+ mFakeEventHub->addKey(DEVICE_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_RIGHT, 0, AKEYCODE_DPAD_RIGHT, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_DOWN, 0, AKEYCODE_DPAD_DOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
+
KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
addConfigurationProperty("keyboard.orientationAware", "1");
@@ -1731,7 +1841,7 @@
setDisplayInfoAndReconfigure(DISPLAY_ID,
DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_270);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 1, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, args.action);
ASSERT_EQ(KEY_UP, args.scanCode);
@@ -1740,7 +1850,7 @@
setDisplayInfoAndReconfigure(DISPLAY_ID,
DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_180);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, AKEYCODE_DPAD_UP, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, KEY_UP, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(AKEY_EVENT_ACTION_UP, args.action);
ASSERT_EQ(KEY_UP, args.scanCode);
@@ -1776,7 +1886,7 @@
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
addMapperAndConfigure(mapper);
- mFakeEventHub->addKey(DEVICE_ID, KEY_A, AKEYCODE_A, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_A, 0, AKEYCODE_A, 0);
const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
uint8_t flags[2] = { 0, 0 };
@@ -1789,6 +1899,9 @@
mFakeEventHub->addLed(DEVICE_ID, LED_CAPSL, true /*initially on*/);
mFakeEventHub->addLed(DEVICE_ID, LED_NUML, false /*initially off*/);
mFakeEventHub->addLed(DEVICE_ID, LED_SCROLLL, false /*initially off*/);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_CAPSLOCK, 0, AKEYCODE_CAPS_LOCK, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_NUMLOCK, 0, AKEYCODE_NUM_LOCK, 0);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper* mapper = new KeyboardInputMapper(mDevice,
AINPUT_SOURCE_KEYBOARD, AINPUT_KEYBOARD_TYPE_ALPHABETIC);
@@ -1801,9 +1914,9 @@
// Toggle caps lock on.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+ EV_KEY, KEY_CAPSLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+ EV_KEY, KEY_CAPSLOCK, 0);
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1811,9 +1924,9 @@
// Toggle num lock on.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+ EV_KEY, KEY_NUMLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+ EV_KEY, KEY_NUMLOCK, 0);
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1821,9 +1934,9 @@
// Toggle caps lock off.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 1, 0);
+ EV_KEY, KEY_CAPSLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK, 0, 0);
+ EV_KEY, KEY_CAPSLOCK, 0);
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1831,9 +1944,9 @@
// Toggle scroll lock on.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+ EV_KEY, KEY_SCROLLLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+ EV_KEY, KEY_SCROLLLOCK, 0);
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1841,9 +1954,9 @@
// Toggle num lock off.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 1, 0);
+ EV_KEY, KEY_NUMLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_NUMLOCK, AKEYCODE_NUM_LOCK, 0, 0);
+ EV_KEY, KEY_NUMLOCK, 0);
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_TRUE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1851,9 +1964,9 @@
// Toggle scroll lock off.
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 1, 0);
+ EV_KEY, KEY_SCROLLLOCK, 1);
process(mapper, ARBITRARY_TIME, DEVICE_ID,
- EV_KEY, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0, 0);
+ EV_KEY, KEY_SCROLLLOCK, 0);
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_CAPSL));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_NUML));
ASSERT_FALSE(mFakeEventHub->getLedState(DEVICE_ID, LED_SCROLLL));
@@ -1886,9 +1999,9 @@
int32_t originalX, int32_t originalY, int32_t rotatedX, int32_t rotatedY) {
NotifyMotionArgs args;
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, originalX, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, originalY, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, originalX);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, originalY);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -1974,8 +2087,8 @@
// Button press.
// Mostly testing non x/y behavior here so we don't need to check again elsewhere.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -1996,8 +2109,8 @@
ASSERT_EQ(ARBITRARY_TIME, args.downTime);
// Button release. Should have same down time.
- process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
- process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME + 1, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ARBITRARY_TIME + 1, args.eventTime);
ASSERT_EQ(DEVICE_ID, args.deviceId);
@@ -2026,16 +2139,16 @@
NotifyMotionArgs args;
// Motion in X but not Y.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
1.0f / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// Motion in Y but not X.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2050,16 +2163,16 @@
NotifyMotionArgs args;
// Button press.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// Button release.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2074,10 +2187,10 @@
NotifyMotionArgs args;
// Combined X, Y and Button.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, -2, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, -2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2085,9 +2198,9 @@
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// Move X, Y a bit while pressed.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 2, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 2);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2095,8 +2208,8 @@
1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// Release Button.
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MOUSE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2185,8 +2298,8 @@
NotifyKeyArgs keyArgs;
// press BTN_LEFT, release BTN_LEFT
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
@@ -2194,8 +2307,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_LEFT, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2211,9 +2324,9 @@
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
@@ -2223,8 +2336,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_RIGHT, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, mFakePointerController->getButtonState());
@@ -2232,8 +2345,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_MIDDLE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2248,8 +2361,8 @@
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
// press BTN_BACK, release BTN_BACK
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -2260,8 +2373,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_BACK, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2273,8 +2386,8 @@
ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
// press BTN_SIDE, release BTN_SIDE
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
@@ -2285,8 +2398,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_SIDE, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2298,8 +2411,8 @@
ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
// press BTN_FORWARD, release BTN_FORWARD
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -2310,8 +2423,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_FORWARD, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2323,8 +2436,8 @@
ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
// press BTN_EXTRA, release BTN_EXTRA
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
@@ -2335,8 +2448,8 @@
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0],
100.0f, 200.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0, 0, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_EXTRA, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(0, motionArgs.buttonState);
ASSERT_EQ(0, mFakePointerController->getButtonState());
@@ -2359,9 +2472,9 @@
NotifyMotionArgs args;
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 0, 10, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 0, 20, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
@@ -2462,8 +2575,8 @@
void TouchInputMapperTest::prepareVirtualKeys() {
mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[0]);
mFakeEventHub->addVirtualKeyDefinition(DEVICE_ID, VIRTUAL_KEYS[1]);
- mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, AKEYCODE_HOME, POLICY_FLAG_WAKE);
- mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, AKEYCODE_MENU, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(DEVICE_ID, KEY_MENU, 0, AKEYCODE_MENU, POLICY_FLAG_WAKE);
}
int32_t TouchInputMapperTest::toRawX(float displayX) {
@@ -2502,7 +2615,7 @@
};
void SingleTouchInputMapperTest::prepareButtons() {
- mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, AKEYCODE_UNKNOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
}
void SingleTouchInputMapperTest::prepareAxes(int axes) {
@@ -2533,48 +2646,48 @@
}
void SingleTouchInputMapperTest::processDown(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 1, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 1);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
}
void SingleTouchInputMapperTest::processMove(SingleTouchInputMapper* mapper, int32_t x, int32_t y) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, 0, x, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, 0, y, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_Y, y);
}
void SingleTouchInputMapperTest::processUp(SingleTouchInputMapper* mapper) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, BTN_TOUCH, 0);
}
void SingleTouchInputMapperTest::processPressure(
SingleTouchInputMapper* mapper, int32_t pressure) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, 0, pressure, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_PRESSURE, pressure);
}
void SingleTouchInputMapperTest::processToolMajor(
SingleTouchInputMapper* mapper, int32_t toolMajor) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, 0, toolMajor, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TOOL_WIDTH, toolMajor);
}
void SingleTouchInputMapperTest::processDistance(
SingleTouchInputMapper* mapper, int32_t distance) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, 0, distance, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_DISTANCE, distance);
}
void SingleTouchInputMapperTest::processTilt(
SingleTouchInputMapper* mapper, int32_t tiltX, int32_t tiltY) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, 0, tiltX, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, 0, tiltY, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_X, tiltX);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_TILT_Y, tiltY);
}
void SingleTouchInputMapperTest::processKey(
SingleTouchInputMapper* mapper, int32_t code, int32_t value) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, 0, value, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
}
void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper* mapper) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
}
@@ -3464,7 +3577,7 @@
prepareDisplay(DISPLAY_ORIENTATION_0);
prepareButtons();
prepareAxes(POSITION);
- mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, AKEYCODE_UNKNOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
addMapperAndConfigure(mapper);
NotifyMotionArgs motionArgs;
@@ -3678,71 +3791,71 @@
void MultiTouchInputMapperTest::processPosition(
MultiTouchInputMapper* mapper, int32_t x, int32_t y) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, 0, x, 0);
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, 0, y, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_X, x);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_POSITION_Y, y);
}
void MultiTouchInputMapperTest::processTouchMajor(
MultiTouchInputMapper* mapper, int32_t touchMajor) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, 0, touchMajor, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MAJOR, touchMajor);
}
void MultiTouchInputMapperTest::processTouchMinor(
MultiTouchInputMapper* mapper, int32_t touchMinor) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, 0, touchMinor, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOUCH_MINOR, touchMinor);
}
void MultiTouchInputMapperTest::processToolMajor(
MultiTouchInputMapper* mapper, int32_t toolMajor) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, 0, toolMajor, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MAJOR, toolMajor);
}
void MultiTouchInputMapperTest::processToolMinor(
MultiTouchInputMapper* mapper, int32_t toolMinor) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, 0, toolMinor, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_WIDTH_MINOR, toolMinor);
}
void MultiTouchInputMapperTest::processOrientation(
MultiTouchInputMapper* mapper, int32_t orientation) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, 0, orientation, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_ORIENTATION, orientation);
}
void MultiTouchInputMapperTest::processPressure(
MultiTouchInputMapper* mapper, int32_t pressure) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, 0, pressure, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_PRESSURE, pressure);
}
void MultiTouchInputMapperTest::processDistance(
MultiTouchInputMapper* mapper, int32_t distance) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, 0, distance, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_DISTANCE, distance);
}
void MultiTouchInputMapperTest::processId(
MultiTouchInputMapper* mapper, int32_t id) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, 0, id, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TRACKING_ID, id);
}
void MultiTouchInputMapperTest::processSlot(
MultiTouchInputMapper* mapper, int32_t slot) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, 0, slot, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_SLOT, slot);
}
void MultiTouchInputMapperTest::processToolType(
MultiTouchInputMapper* mapper, int32_t toolType) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, 0, toolType, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_ABS, ABS_MT_TOOL_TYPE, toolType);
}
void MultiTouchInputMapperTest::processKey(
MultiTouchInputMapper* mapper, int32_t code, int32_t value) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, 0, value, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_KEY, code, value);
}
void MultiTouchInputMapperTest::processMTSync(MultiTouchInputMapper* mapper) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_MT_REPORT, 0);
}
void MultiTouchInputMapperTest::processSync(MultiTouchInputMapper* mapper) {
- process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0, 0, 0);
+ process(mapper, ARBITRARY_TIME, DEVICE_ID, EV_SYN, SYN_REPORT, 0);
}
@@ -4891,7 +5004,7 @@
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareDisplay(DISPLAY_ORIENTATION_0);
prepareAxes(POSITION | ID | SLOT);
- mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, AKEYCODE_UNKNOWN, 0);
+ mFakeEventHub->addKey(DEVICE_ID, BTN_TOUCH, 0, AKEYCODE_UNKNOWN, 0);
addMapperAndConfigure(mapper);
NotifyMotionArgs motionArgs;
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 352decf..359074a 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -875,6 +875,19 @@
return null;
}
+ @Override
+ public boolean isActiveNetworkMetered() {
+ enforceAccessPermission();
+ final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+ if (state != null) {
+ try {
+ return mPolicyManager.isNetworkMetered(state);
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
public boolean setRadios(boolean turnOn) {
boolean result = true;
enforceChangePermission();
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 3db4601..b22be76 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -100,7 +100,7 @@
private int mDisabledNotifications;
private NotificationRecord mVibrateNotification;
- private Vibrator mVibrator = new Vibrator();
+ private Vibrator mVibrator;
// for enabling and disabling notification pulse behavior
private boolean mScreenOn = true;
@@ -398,6 +398,7 @@
{
super();
mContext = context;
+ mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mAm = ActivityManagerNative.getDefault();
mSound = new NotificationPlayer(TAG);
mSound.setUsesWakeLock(context);
diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java
index 768be7d..a3ac8d0 100644
--- a/services/java/com/android/server/NsdService.java
+++ b/services/java/com/android/server/NsdService.java
@@ -32,9 +32,11 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.net.InetAddress;
import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
+import java.util.HashMap;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
import com.android.internal.app.IBatteryStats;
import com.android.internal.telephony.TelephonyIntents;
@@ -60,10 +62,13 @@
/**
* Clients receiving asynchronous messages
*/
- private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>();
+ private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
private AsyncChannel mReplyChannel = new AsyncChannel();
+ private int INVALID_ID = 0;
+ private int mUniqueId = 1;
+
/**
* Handles client(app) connections
*/
@@ -75,13 +80,19 @@
@Override
public void handleMessage(Message msg) {
+ ClientInfo clientInfo;
+ DnsSdServiceInfo servInfo;
switch (msg.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
AsyncChannel c = (AsyncChannel) msg.obj;
if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
- mClients.add(c);
+ ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
+ if (mClients.size() == 0) {
+ startMDnsDaemon();
+ }
+ mClients.put(msg.replyTo, cInfo);
} else {
Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
}
@@ -92,7 +103,10 @@
} else {
if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
}
- mClients.remove((AsyncChannel) msg.obj);
+ mClients.remove(msg.replyTo);
+ if (mClients.size() == 0) {
+ stopMDnsDaemon();
+ }
break;
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
AsyncChannel ac = new AsyncChannel();
@@ -100,22 +114,98 @@
break;
case NsdManager.DISCOVER_SERVICES:
if (DBG) Slog.d(TAG, "Discover services");
- DnsSdServiceInfo s = (DnsSdServiceInfo) msg.obj;
- discoverServices(1, s.getServiceType());
- mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
+ servInfo = (DnsSdServiceInfo) msg.obj;
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mDiscoveryId != INVALID_ID) {
+ //discovery already in progress
+ if (DBG) Slog.d(TAG, "discovery in progress");
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ clientInfo.mDiscoveryId = getUniqueId();
+ if (discoverServices(clientInfo.mDiscoveryId, servInfo.getServiceType())) {
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+ NsdManager.ERROR);
+ clientInfo.mDiscoveryId = INVALID_ID;
+ }
break;
case NsdManager.STOP_DISCOVERY:
if (DBG) Slog.d(TAG, "Stop service discovery");
- mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED);
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mDiscoveryId == INVALID_ID) {
+ //already stopped
+ if (DBG) Slog.d(TAG, "discovery already stopped");
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ if (stopServiceDiscovery(clientInfo.mDiscoveryId)) {
+ clientInfo.mDiscoveryId = INVALID_ID;
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+ NsdManager.ERROR);
+ }
break;
case NsdManager.REGISTER_SERVICE:
if (DBG) Slog.d(TAG, "Register service");
- mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED);
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mRegisteredIds.size() >= ClientInfo.MAX_REG) {
+ if (DBG) Slog.d(TAG, "register service exceeds limit");
+ mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+ NsdManager.MAX_REGS_REACHED);
+ }
+
+ int id = getUniqueId();
+ if (registerService(id, (DnsSdServiceInfo) msg.obj)) {
+ clientInfo.mRegisteredIds.add(id);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+ NsdManager.ERROR);
+ }
break;
case NsdManager.UPDATE_SERVICE:
if (DBG) Slog.d(TAG, "Update service");
+ //TODO: implement
mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED);
break;
+ case NsdManager.RESOLVE_SERVICE:
+ if (DBG) Slog.d(TAG, "Resolve service");
+ servInfo = (DnsSdServiceInfo) msg.obj;
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mResolveId != INVALID_ID) {
+ //first cancel existing resolve
+ stopResolveService(clientInfo.mResolveId);
+ }
+
+ clientInfo.mResolveId = getUniqueId();
+ if (!resolveService(clientInfo.mResolveId, servInfo)) {
+ mReplyChannel.replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+ NsdManager.ERROR);
+ clientInfo.mResolveId = INVALID_ID;
+ }
+ break;
+ case NsdManager.STOP_RESOLVE:
+ if (DBG) Slog.d(TAG, "Stop resolve");
+ clientInfo = mClients.get(msg.replyTo);
+ if (clientInfo.mResolveId == INVALID_ID) {
+ //already stopped
+ if (DBG) Slog.d(TAG, "resolve already stopped");
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+ NsdManager.ALREADY_ACTIVE);
+ break;
+ }
+ if (stopResolveService(clientInfo.mResolveId)) {
+ clientInfo.mResolveId = INVALID_ID;
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_SUCCEEDED);
+ } else {
+ mReplyChannel.replyToMessage(msg, NsdManager.STOP_RESOLVE_FAILED,
+ NsdManager.ERROR);
+ }
+ break;
default:
Slog.d(TAG, "NsdServicehandler.handleMessage ignoring msg=" + msg);
break;
@@ -134,12 +224,10 @@
nsdThread.start();
mAsyncServiceHandler = new AsyncServiceHandler(nsdThread.getLooper());
- /*
mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
MDNS_TAG, 25);
Thread th = new Thread(mNativeConnector, MDNS_TAG);
th.start();
- */
}
public static NsdService create(Context context) throws InterruptedException {
@@ -152,22 +240,29 @@
return new Messenger(mAsyncServiceHandler);
}
- /* These should be in sync with system/netd/mDnsResponseCode.h */
- class NativeResponseCode {
- public static final int SERVICE_FOUND = 101;
- public static final int SERVICE_LOST = 102;
- public static final int SERVICE_DISCOVERY_FAILED = 103;
-
- public static final int SERVICE_REGISTERED = 104;
- public static final int SERVICE_REGISTRATION_FAILED = 105;
-
- public static final int SERVICE_UPDATED = 106;
- public static final int SERVICE_UPDATE_FAILED = 107;
-
- public static final int SERVICE_RESOLVED = 108;
- public static final int SERVICE_RESOLUTION_FAILED = 109;
+ private int getUniqueId() {
+ if (++mUniqueId == INVALID_ID) return ++mUniqueId;
+ return mUniqueId;
}
+ /* These should be in sync with system/netd/mDnsResponseCode.h */
+ class NativeResponseCode {
+ public static final int SERVICE_DISCOVERY_FAILED = 602;
+ public static final int SERVICE_FOUND = 603;
+ public static final int SERVICE_LOST = 604;
+
+ public static final int SERVICE_REGISTRATION_FAILED = 605;
+ public static final int SERVICE_REGISTERED = 606;
+
+ public static final int SERVICE_RESOLUTION_FAILED = 607;
+ public static final int SERVICE_RESOLVED = 608;
+
+ public static final int SERVICE_UPDATED = 609;
+ public static final int SERVICE_UPDATE_FAILED = 610;
+
+ public static final int SERVICE_GET_ADDR_FAILED = 611;
+ public static final int SERVICE_GET_ADDR_SUCCESS = 612;
+ }
class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
public void onDaemonConnected() {
@@ -175,21 +270,55 @@
}
public boolean onEvent(int code, String raw, String[] cooked) {
+ ClientInfo clientInfo;
+ DnsSdServiceInfo servInfo;
+ int id = Integer.parseInt(cooked[1]);
switch (code) {
case NativeResponseCode.SERVICE_FOUND:
- /* NNN uniqueId serviceName regType */
+ /* NNN uniqueId serviceName regType domain */
+ if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
+ clientInfo = getClientByDiscovery(id);
+ if (clientInfo == null) break;
+
+ servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null);
+ clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, servInfo);
break;
case NativeResponseCode.SERVICE_LOST:
- /* NNN uniqueId serviceName regType */
+ /* NNN uniqueId serviceName regType domain */
+ if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
+ clientInfo = getClientByDiscovery(id);
+ if (clientInfo == null) break;
+
+ servInfo = new DnsSdServiceInfo(cooked[2], cooked[3], null);
+ clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, servInfo);
break;
case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
/* NNN uniqueId errorCode */
+ if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
+ clientInfo = getClientByDiscovery(id);
+ if (clientInfo == null) break;
+
+ clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
+ NsdManager.ERROR);
break;
case NativeResponseCode.SERVICE_REGISTERED:
/* NNN regId serviceName regType */
+ if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
+ clientInfo = getClientByRegistration(id);
+ if (clientInfo == null) break;
+
+ servInfo = new DnsSdServiceInfo(cooked[2], null, null);
+ clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
+ id, 0, servInfo);
break;
case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
/* NNN regId errorCode */
+ if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
+ clientInfo = getClientByRegistration(id);
+ if (clientInfo == null) break;
+
+ clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
+ NsdManager.ERROR);
break;
case NativeResponseCode.SERVICE_UPDATED:
/* NNN regId */
@@ -199,9 +328,52 @@
break;
case NativeResponseCode.SERVICE_RESOLVED:
/* NNN resolveId fullName hostName port txtlen txtdata */
+ if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
+ clientInfo = getClientByResolve(id);
+ if (clientInfo == null) break;
+
+ int index = cooked[2].indexOf(".");
+ if (index == -1) {
+ Slog.e(TAG, "Invalid service found " + raw);
+ break;
+ }
+ String name = cooked[2].substring(0, index);
+ String rest = cooked[2].substring(index);
+ String type = rest.replace(".local.", "");
+
+ clientInfo.mResolvedService = new DnsSdServiceInfo(name, type, null);
+ clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
+
+ stopResolveService(id);
+ getAddrInfo(id, cooked[3]);
break;
case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
- /* NNN resovleId errorCode */
+ case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
+ /* NNN resolveId errorCode */
+ if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
+ clientInfo = getClientByResolve(id);
+ if (clientInfo == null) break;
+
+ clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+ NsdManager.ERROR);
+ break;
+ case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
+ /* NNN resolveId hostname ttl addr */
+ if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
+ clientInfo = getClientByResolve(id);
+ if (clientInfo == null || clientInfo.mResolvedService == null) break;
+
+ try {
+ clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
+ clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
+ clientInfo.mResolvedService);
+ clientInfo.mResolvedService = null;
+ clientInfo.mResolveId = INVALID_ID;
+ } catch (java.net.UnknownHostException e) {
+ clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+ NsdManager.ERROR);
+ }
+ stopGetAddrInfo(id);
break;
default:
break;
@@ -210,48 +382,129 @@
}
}
- private void registerService(int regId, DnsSdServiceInfo service) {
+ private boolean startMDnsDaemon() {
+ if (DBG) Slog.d(TAG, "startMDnsDaemon");
+ try {
+ mNativeConnector.execute("mdnssd", "start-service");
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to start daemon" + e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean stopMDnsDaemon() {
+ if (DBG) Slog.d(TAG, "stopMDnsDaemon");
+ try {
+ mNativeConnector.execute("mdnssd", "stop-service");
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to start daemon" + e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean registerService(int regId, DnsSdServiceInfo service) {
+ if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
try {
//Add txtlen and txtdata
mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(),
service.getServiceType(), service.getPort());
} catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to execute registerService");
+ Slog.e(TAG, "Failed to execute registerService " + e);
+ return false;
}
+ return true;
}
- private void updateService(int regId, DnsSdTxtRecord t) {
+ private boolean unregisterService(int regId) {
+ if (DBG) Slog.d(TAG, "unregisterService: " + regId);
try {
- if (t == null) return;
+ mNativeConnector.execute("mdnssd", "stop-register", regId);
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to execute unregisterService " + e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean updateService(int regId, DnsSdTxtRecord t) {
+ if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
+ try {
+ if (t == null) return false;
mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
} catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to updateServices");
+ Slog.e(TAG, "Failed to updateServices " + e);
+ return false;
}
+ return true;
}
- private void discoverServices(int discoveryId, String serviceType) {
+ private boolean discoverServices(int discoveryId, String serviceType) {
+ if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
try {
mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
} catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to discoverServices");
+ Slog.e(TAG, "Failed to discoverServices " + e);
+ return false;
}
+ return true;
}
- private void stopServiceDiscovery(int discoveryId) {
+ private boolean stopServiceDiscovery(int discoveryId) {
+ if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
try {
- mNativeConnector.execute("mdnssd", "stopdiscover", discoveryId);
+ mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
} catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to stopServiceDiscovery");
+ Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
+ return false;
}
+ return true;
}
- private void resolveService(DnsSdServiceInfo service) {
+ private boolean resolveService(int resolveId, DnsSdServiceInfo service) {
+ if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
try {
- mNativeConnector.execute("mdnssd", "resolve", service.getServiceName(),
- service.getServiceType());
+ mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
+ service.getServiceType(), "local.");
} catch(NativeDaemonConnectorException e) {
- Slog.e(TAG, "Failed to resolveService");
+ Slog.e(TAG, "Failed to resolveService " + e);
+ return false;
}
+ return true;
+ }
+
+ private boolean stopResolveService(int resolveId) {
+ if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
+ try {
+ mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to stop resolve " + e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean getAddrInfo(int resolveId, String hostname) {
+ if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
+ try {
+ mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to getAddrInfo " + e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean stopGetAddrInfo(int resolveId) {
+ if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
+ try {
+ mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
+ } catch(NativeDaemonConnectorException e) {
+ Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
+ return false;
+ }
+ return true;
}
@Override
@@ -266,4 +519,51 @@
pw.println("Internal state:");
}
+
+ private ClientInfo getClientByDiscovery(int discoveryId) {
+ for (ClientInfo c: mClients.values()) {
+ if (c.mDiscoveryId == discoveryId) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ private ClientInfo getClientByResolve(int resolveId) {
+ for (ClientInfo c: mClients.values()) {
+ if (c.mResolveId == resolveId) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ private ClientInfo getClientByRegistration(int regId) {
+ for (ClientInfo c: mClients.values()) {
+ if (c.mRegisteredIds.contains(regId)) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ /* Information tracked per client */
+ private class ClientInfo {
+
+ private static final int MAX_REG = 5;
+ private AsyncChannel mChannel;
+ private Messenger mMessenger;
+ private int mDiscoveryId;
+ private int mResolveId;
+ /* Remembers a resolved service until getaddrinfo completes */
+ private DnsSdServiceInfo mResolvedService;
+ private ArrayList<Integer> mRegisteredIds = new ArrayList<Integer>();
+
+ private ClientInfo(AsyncChannel c, Messenger m) {
+ mChannel = c;
+ mMessenger = m;
+ mDiscoveryId = mResolveId = INVALID_ID;
+ if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+ }
+ }
}
diff --git a/services/java/com/android/server/RecognitionManagerService.java b/services/java/com/android/server/RecognitionManagerService.java
index 8e55512..85224d8 100644
--- a/services/java/com/android/server/RecognitionManagerService.java
+++ b/services/java/com/android/server/RecognitionManagerService.java
@@ -75,7 +75,10 @@
try {
mContext.getPackageManager().getServiceInfo(comp, 0);
} catch (NameNotFoundException e) {
- setCurRecognizer(null);
+ comp = findAvailRecognizer(null);
+ if (comp != null) {
+ setCurRecognizer(comp);
+ }
}
} else {
comp = findAvailRecognizer(null);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 241d04ff..75dcf8c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -115,6 +115,7 @@
LightsService lights = null;
PowerManagerService power = null;
BatteryService battery = null;
+ VibratorService vibrator = null;
AlarmManagerService alarm = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
@@ -203,7 +204,8 @@
ServiceManager.addService("battery", battery);
Slog.i(TAG, "Vibrator Service");
- ServiceManager.addService("vibrator", new VibratorService(context));
+ vibrator = new VibratorService(context);
+ ServiceManager.addService("vibrator", vibrator);
// only initialize the power service after we have started the
// lights service, content providers and the battery service.
@@ -645,6 +647,12 @@
// It is now time to start up the app processes...
+ try {
+ vibrator.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Vibrator Service ready", e);
+ }
+
if (devicePolicy != null) {
try {
devicePolicy.systemReady();
diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java
index de25747..6282c31 100755
--- a/services/java/com/android/server/VibratorService.java
+++ b/services/java/com/android/server/VibratorService.java
@@ -21,6 +21,8 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.IVibratorService;
import android.os.PowerManager;
@@ -29,18 +31,41 @@
import android.os.IBinder;
import android.os.Binder;
import android.os.SystemClock;
+import android.os.Vibrator;
import android.os.WorkSource;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
import android.util.Slog;
+import android.view.InputDevice;
+import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
-public class VibratorService extends IVibratorService.Stub {
+public class VibratorService extends IVibratorService.Stub
+ implements InputManager.InputDeviceListener {
private static final String TAG = "VibratorService";
private final LinkedList<Vibration> mVibrations;
private Vibration mCurrentVibration;
private final WorkSource mTmpWorkSource = new WorkSource();
+ private final Handler mH = new Handler();
+
+ private final Context mContext;
+ private final PowerManager.WakeLock mWakeLock;
+ private InputManager mIm;
+
+ volatile VibrateThread mThread;
+
+ // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+ // to be acquired
+ private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
+ private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
+ private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+
+ native static boolean vibratorExists();
+ native static void vibratorOn(long milliseconds);
+ native static void vibratorOff();
private class Vibration implements IBinder.DeathRecipient {
private final IBinder mToken;
@@ -112,10 +137,23 @@
context.registerReceiver(mIntentReceiver, filter);
}
- public boolean hasVibrator() {
- return vibratorExists();
+ public void systemReady() {
+ mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,
+ new ContentObserver(mH) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateInputDeviceVibrators();
+ }
+ });
+ updateInputDeviceVibrators();
}
-
+
+ public boolean hasVibrator() {
+ return doVibratorExists();
+ }
+
public void vibrate(long milliseconds, IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
!= PackageManager.PERMISSION_GRANTED) {
@@ -131,6 +169,7 @@
// longer than milliseconds.
return;
}
+
Vibration vib = new Vibration(token, milliseconds, uid);
synchronized (mVibrations) {
removeVibrationLocked(token);
@@ -240,7 +279,7 @@
}
mThread = null;
}
- vibratorOff();
+ doVibratorOff();
mH.removeCallbacks(mVibrationRunnable);
}
@@ -257,7 +296,7 @@
// Lock held on mVibrations
private void startVibrationLocked(final Vibration vib) {
if (vib.mTimeout != 0) {
- vibratorOn(vib.mTimeout);
+ doVibratorOn(vib.mTimeout);
mH.postDelayed(mVibrationRunnable, vib.mTimeout);
} else {
// mThread better be null here. doCancelVibrate should always be
@@ -295,6 +334,94 @@
}
}
+ private void updateInputDeviceVibrators() {
+ synchronized (mVibrations) {
+ doCancelVibrateLocked();
+
+ synchronized (mInputDeviceVibrators) {
+ mVibrateInputDevicesSetting = false;
+ try {
+ mVibrateInputDevicesSetting = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.VIBRATE_INPUT_DEVICES) > 0;
+ } catch (SettingNotFoundException snfe) {
+ }
+
+ if (mVibrateInputDevicesSetting) {
+ if (!mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = true;
+ mIm.registerInputDeviceListener(this, mH);
+ }
+ } else {
+ if (mInputDeviceListenerRegistered) {
+ mInputDeviceListenerRegistered = false;
+ mIm.unregisterInputDeviceListener(this);
+ }
+ }
+
+ mInputDeviceVibrators.clear();
+ if (mVibrateInputDevicesSetting) {
+ int[] ids = mIm.getInputDeviceIds();
+ for (int i = 0; i < ids.length; i++) {
+ InputDevice device = mIm.getInputDevice(ids[i]);
+ Vibrator vibrator = device.getVibrator();
+ if (vibrator.hasVibrator()) {
+ mInputDeviceVibrators.add(vibrator);
+ }
+ }
+ }
+ }
+
+ startNextVibrationLocked();
+ }
+ }
+
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ updateInputDeviceVibrators();
+ }
+
+ private boolean doVibratorExists() {
+ synchronized (mInputDeviceVibrators) {
+ return !mInputDeviceVibrators.isEmpty() || vibratorExists();
+ }
+ }
+
+ private void doVibratorOn(long millis) {
+ synchronized (mInputDeviceVibrators) {
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount != 0) {
+ for (int i = 0; i < vibratorCount; i++) {
+ mInputDeviceVibrators.get(i).vibrate(millis);
+ }
+ } else {
+ vibratorOn(millis);
+ }
+ }
+ }
+
+ private void doVibratorOff() {
+ synchronized (mInputDeviceVibrators) {
+ final int vibratorCount = mInputDeviceVibrators.size();
+ if (vibratorCount != 0) {
+ for (int i = 0; i < vibratorCount; i++) {
+ mInputDeviceVibrators.get(i).cancel();
+ }
+ } else {
+ vibratorOff();
+ }
+ }
+ }
+
private class VibrateThread extends Thread {
final Vibration mVibration;
boolean mDone;
@@ -350,7 +477,7 @@
// duration is saved for delay() at top of loop
duration = pattern[index++];
if (duration > 0) {
- VibratorService.this.vibratorOn(duration);
+ VibratorService.this.doVibratorOn(duration);
}
} else {
if (repeat < 0) {
@@ -394,15 +521,4 @@
}
}
};
-
- private Handler mH = new Handler();
-
- private final Context mContext;
- private final PowerManager.WakeLock mWakeLock;
-
- volatile VibrateThread mThread;
-
- native static boolean vibratorExists();
- native static void vibratorOn(long milliseconds);
- native static void vibratorOff();
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 31aa21e..889fbe4 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -16,6 +16,7 @@
package com.android.server.accessibility;
+import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.input.InputFilter;
import android.content.Context;
@@ -36,6 +37,8 @@
private final Context mContext;
+ private final GestureListener mGestureListener;
+
/**
* This is an interface for explorers that take a {@link MotionEvent}
* stream and perform touch exploration of the screen content.
@@ -64,11 +67,13 @@
}
private TouchExplorer mTouchExplorer;
+
private int mTouchscreenSourceDeviceId;
- public AccessibilityInputFilter(Context context) {
+ public AccessibilityInputFilter(Context context, GestureListener gestureListener) {
super(context.getMainLooper());
mContext = context;
+ mGestureListener = gestureListener;
}
@Override
@@ -76,7 +81,7 @@
if (DEBUG) {
Slog.d(TAG, "Accessibility input filter installed.");
}
- mTouchExplorer = new TouchExplorer(this, mContext);
+ mTouchExplorer = new TouchExplorer(this, mContext, mGestureListener);
super.onInstalled();
}
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index c99aa02..754a4dd 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,11 +16,14 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityServiceInfo.DEFAULT;
+import static android.accessibilityservice.AccessibilityServiceInfo.INCLUDE_NOT_IMPORTANT_VIEWS;
+
import android.Manifest;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
import android.accessibilityservice.IAccessibilityServiceConnection;
-import android.accessibilityservice.IEventListener;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -35,6 +38,7 @@
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -55,8 +59,7 @@
import android.view.accessibility.IAccessibilityManagerClient;
import com.android.internal.content.PackageMonitor;
-import com.android.internal.os.HandlerCaller;
-import com.android.internal.os.HandlerCaller.SomeArgs;
+import com.android.server.accessibility.TouchExplorer.GestureListener;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParserException;
@@ -80,7 +83,7 @@
* @hide
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
- implements HandlerCaller.Callback {
+ implements GestureListener {
private static final boolean DEBUG = false;
@@ -93,12 +96,8 @@
private static final int OWN_PROCESS_ID = android.os.Process.myPid();
- private static final int DO_SET_SERVICE_INFO = 10;
-
private static int sNextWindowId;
- final HandlerCaller mCaller;
-
final Context mContext;
final Object mLock = new Object();
@@ -152,7 +151,7 @@
int eventType = message.arg1;
synchronized (mLock) {
- notifyEventListenerLocked(service, eventType);
+ notifyAccessibilityEventLocked(service, eventType);
}
}
};
@@ -165,7 +164,6 @@
public AccessibilityManagerService(Context context) {
mContext = context;
mPackageManager = mContext.getPackageManager();
- mCaller = new HandlerCaller(context, this);
mWindowManagerService = (WindowManagerService) ServiceManager.getService(
Context.WINDOW_SERVICE);
mSecurityPolicy = new SecurityPolicy();
@@ -395,32 +393,6 @@
}
}
- public void executeMessage(Message message) {
- switch (message.what) {
- case DO_SET_SERVICE_INFO: {
- SomeArgs arguments = ((SomeArgs) message.obj);
-
- AccessibilityServiceInfo info = (AccessibilityServiceInfo) arguments.arg1;
- Service service = (Service) arguments.arg2;
-
- synchronized (mLock) {
- // If the XML manifest had data to configure the service its info
- // should be already set. In such a case update only the dynamically
- // configurable properties.
- AccessibilityServiceInfo oldInfo = service.mAccessibilityServiceInfo;
- if (oldInfo != null) {
- oldInfo.updateDynamicallyConfigurableProperties(info);
- service.setDynamicallyConfigurableProperties(oldInfo);
- } else {
- service.setDynamicallyConfigurableProperties(info);
- }
- }
- } return;
- default:
- Slog.w(LOG_TAG, "Unknown message type: " + message.what);
- }
- }
-
public int addAccessibilityInteractionConnection(IWindow windowToken,
IAccessibilityInteractionConnection connection) throws RemoteException {
synchronized (mLock) {
@@ -455,7 +427,7 @@
}
}
- public void registerUiTestAutomationService(IEventListener listener,
+ public void registerUiTestAutomationService(IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo) {
mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
@@ -480,18 +452,45 @@
}
// Hook the automation service up.
mUiAutomationService = new Service(componentName, accessibilityServiceInfo, true);
- mUiAutomationService.onServiceConnected(componentName, listener.asBinder());
+ mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder());
}
- public void unregisterUiTestAutomationService(IEventListener listener) {
+ public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
synchronized (mLock) {
// Automation service is not bound, so pretend it died to perform clean up.
- if (mUiAutomationService != null) {
+ if (mUiAutomationService != null
+ && mUiAutomationService.mServiceInterface == serviceClient) {
mUiAutomationService.binderDied();
}
}
}
+ @Override
+ public void onGesture(int gestureId) {
+ synchronized (mLock) {
+ final boolean dispatched = notifyGestureLocked(gestureId, false);
+ if (!dispatched) {
+ notifyGestureLocked(gestureId, true);
+ }
+ }
+ }
+
+ private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
+ final int serviceCount = mServices.size();
+ for (int i = 0; i < serviceCount; i++) {
+ Service service = mServices.get(i);
+ if (service.mIsDefault == isDefault) {
+ try {
+ service.mServiceInterface.onGesture(gestureId);
+ return true;
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error dispatching gesture.");
+ }
+ }
+ }
+ return false;
+ }
+
/**
* Removes an AccessibilityInteractionConnection.
*
@@ -588,13 +587,13 @@
}
/**
- * Notifies a service for a scheduled event given the event type.
+ * Notifies an accessibility service client for a scheduled event given the event type.
*
- * @param service The service.
+ * @param service The service client.
* @param eventType The type of the event to dispatch.
*/
- private void notifyEventListenerLocked(Service service, int eventType) {
- IEventListener listener = service.mServiceInterface;
+ private void notifyAccessibilityEventLocked(Service service, int eventType) {
+ IAccessibilityServiceClient listener = service.mServiceInterface;
// If the service died/was disabled while the message for dispatching
// the accessibility event was propagating the listener may be null.
@@ -699,6 +698,11 @@
return false;
}
+ if (!event.isImportantForAccessibility()
+ && !service.mIncludeNotImportantViews) {
+ return false;
+ }
+
int eventType = event.getEventType();
if ((service.mEventTypes & eventType) != eventType) {
return false;
@@ -864,7 +868,7 @@
if (!mHasInputFilter) {
mHasInputFilter = true;
if (mInputFilter == null) {
- mInputFilter = new AccessibilityInputFilter(mContext);
+ mInputFilter = new AccessibilityInputFilter(mContext, this);
}
mWindowManagerService.setInputFilter(mInputFilter);
}
@@ -942,7 +946,7 @@
IBinder mService;
- IEventListener mServiceInterface;
+ IAccessibilityServiceClient mServiceInterface;
int mEventTypes;
@@ -952,6 +956,8 @@
boolean mIsDefault;
+ boolean mIncludeNotImportantViews;
+
long mNotificationTimeout;
ComponentName mComponentName;
@@ -983,6 +989,7 @@
mContext, 0, new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 0));
} else {
mCanRetrieveScreenContent = true;
+ mIncludeNotImportantViews = true;
}
setDynamicallyConfigurableProperties(accessibilityServiceInfo);
}
@@ -995,7 +1002,17 @@
mPackageNames.addAll(Arrays.asList(packageNames));
}
mNotificationTimeout = info.notificationTimeout;
- mIsDefault = (info.flags & AccessibilityServiceInfo.DEFAULT) != 0;
+ mIsDefault = (info.flags & DEFAULT) != 0;
+
+ final int targetSdkVersion =
+ info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
+ // TODO: Uncomment this line and remove the line below when JellyBean
+ // SDK version is finalized.
+ // if (targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (targetSdkVersion > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ mIncludeNotImportantViews =
+ (info.flags & INCLUDE_NOT_IMPORTANT_VIEWS) != 0;
+ }
synchronized (mLock) {
tryAddServiceLocked(this);
@@ -1043,13 +1060,33 @@
return (mEventTypes != 0 && mFeedbackType != 0 && mService != null);
}
- public void setServiceInfo(AccessibilityServiceInfo info) {
- mCaller.obtainMessageOO(DO_SET_SERVICE_INFO, info, this).sendToTarget();
+ @Override
+ public AccessibilityServiceInfo getServiceInfo() {
+ synchronized (mLock) {
+ return mAccessibilityServiceInfo;
+ }
}
+ @Override
+ public void setServiceInfo(AccessibilityServiceInfo info) {
+ synchronized (mLock) {
+ // If the XML manifest had data to configure the service its info
+ // should be already set. In such a case update only the dynamically
+ // configurable properties.
+ AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
+ if (oldInfo != null) {
+ oldInfo.updateDynamicallyConfigurableProperties(info);
+ setDynamicallyConfigurableProperties(oldInfo);
+ } else {
+ setDynamicallyConfigurableProperties(info);
+ }
+ }
+ }
+
+ @Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
mService = service;
- mServiceInterface = IEventListener.Stub.asInterface(service);
+ mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
try {
mServiceInterface.setConnection(this, mId);
synchronized (mLock) {
@@ -1060,6 +1097,7 @@
}
}
+ @Override
public float findAccessibilityNodeInfoByViewId(int accessibilityWindowId,
long accessibilityNodeId, int viewId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
@@ -1078,11 +1116,13 @@
}
}
}
+ final int flags = (mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.findAccessibilityNodeInfoByViewId(accessibilityNodeId, viewId,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error findAccessibilityNodeInfoByViewId().");
@@ -1093,6 +1133,7 @@
return getCompatibilityScale(resolvedWindowId);
}
+ @Override
public float findAccessibilityNodeInfosByText(int accessibilityWindowId,
long accessibilityNodeId, String text, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
@@ -1112,11 +1153,13 @@
}
}
}
+ final int flags = (mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,
- interactionId, callback, interrogatingPid, interrogatingTid);
+ interactionId, callback, flags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
@@ -1127,10 +1170,47 @@
return getCompatibilityScale(resolvedWindowId);
}
+ @Override
public float findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
long accessibilityNodeId, int interactionId,
- IAccessibilityInteractionConnectionCallback callback, long interrogatingTid,
- int prefetchFlags)
+ IAccessibilityInteractionConnectionCallback callback, int flags,
+ long interrogatingTid) throws RemoteException {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
+ IAccessibilityInteractionConnection connection = null;
+ synchronized (mLock) {
+ mSecurityPolicy.enforceCanRetrieveWindowContent(this);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return 0;
+ } else {
+ connection = getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return 0;
+ }
+ }
+ }
+ final int allFlags = flags | ((mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0);
+ final int interrogatingPid = Binder.getCallingPid();
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
+ interactionId, callback, allFlags, interrogatingPid, interrogatingTid);
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ return getCompatibilityScale(resolvedWindowId);
+ }
+
+ @Override
+ public float findFocus(int accessibilityWindowId, long accessibilityNodeId,
+ int focusType, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
IAccessibilityInteractionConnection connection = null;
@@ -1147,14 +1227,16 @@
}
}
}
+ final int flags = (mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
- connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
- interactionId, callback, prefetchFlags, interrogatingPid, interrogatingTid);
+ connection.findFocus(accessibilityNodeId, interactionId, focusType, callback,
+ flags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
- Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfoByAccessibilityId()");
+ Slog.e(LOG_TAG, "Error calling findAccessibilityFocus()");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
@@ -1162,6 +1244,44 @@
return getCompatibilityScale(resolvedWindowId);
}
+ @Override
+ public float focusSearch(int accessibilityWindowId, long accessibilityNodeId,
+ int direction, int interactionId,
+ IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
+ throws RemoteException {
+ final int resolvedWindowId = resolveAccessibilityWindowId(accessibilityWindowId);
+ IAccessibilityInteractionConnection connection = null;
+ synchronized (mLock) {
+ mSecurityPolicy.enforceCanRetrieveWindowContent(this);
+ final boolean permissionGranted =
+ mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
+ if (!permissionGranted) {
+ return 0;
+ } else {
+ connection = getConnectionLocked(resolvedWindowId);
+ if (connection == null) {
+ return 0;
+ }
+ }
+ }
+ final int flags = (mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
+ final int interrogatingPid = Binder.getCallingPid();
+ final long identityToken = Binder.clearCallingIdentity();
+ try {
+ connection.focusSearch(accessibilityNodeId, interactionId, direction, callback,
+ flags, interrogatingPid, interrogatingTid);
+ } catch (RemoteException re) {
+ if (DEBUG) {
+ Slog.e(LOG_TAG, "Error calling accessibilityFocusSearch()");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+ return getCompatibilityScale(resolvedWindowId);
+ }
+
+ @Override
public boolean performAccessibilityAction(int accessibilityWindowId,
long accessibilityNodeId, int action, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) {
@@ -1179,11 +1299,13 @@
}
}
}
+ final int flags = (mIncludeNotImportantViews) ?
+ AccessibilityNodeInfo.INCLUDE_NOT_IMPORTANT_VIEWS : 0;
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
connection.performAccessibilityAction(accessibilityNodeId, action, interactionId,
- callback, interrogatingPid, interrogatingTid);
+ callback, flags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
@@ -1263,22 +1385,35 @@
}
final class SecurityPolicy {
- private static final int VALID_ACTIONS = AccessibilityNodeInfo.ACTION_FOCUS
- | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS | AccessibilityNodeInfo.ACTION_SELECT
- | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION;
+ private static final int VALID_ACTIONS =
+ AccessibilityNodeInfo.ACTION_CLICK
+ | AccessibilityNodeInfo.ACTION_FOCUS
+ | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS
+ | AccessibilityNodeInfo.ACTION_SELECT
+ | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION
+ | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+ | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
- AccessibilityEvent.TYPE_VIEW_CLICKED | AccessibilityEvent.TYPE_VIEW_FOCUSED
- | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
- | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
- | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_SELECTED
+ AccessibilityEvent.TYPE_VIEW_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
+ | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
+ | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_SELECTED
| AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
| AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
- | AccessibilityEvent.TYPE_VIEW_SCROLLED;
+ | AccessibilityEvent.TYPE_VIEW_SCROLLED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
private static final int RETRIEVAL_ALLOWING_WINDOW_CHANGE_EVENT_TYPES =
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
- | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT;
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
+ | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
+ | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
+ | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
private int mRetrievalAlowingWindowId;
diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java
index d07aa7a..5d01c77 100644
--- a/services/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/java/com/android/server/accessibility/TouchExplorer.java
@@ -20,17 +20,25 @@
import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
import android.content.Context;
+import android.gesture.Gesture;
+import android.gesture.GestureLibraries;
+import android.gesture.GestureLibrary;
+import android.gesture.GesturePoint;
+import android.gesture.GestureStroke;
+import android.gesture.Prediction;
import android.os.Handler;
import android.util.Slog;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import com.android.server.accessibility.AccessibilityInputFilter.Explorer;
import com.android.server.input.InputFilter;
+import com.android.internal.R;
+import java.util.ArrayList;
import java.util.Arrays;
/**
@@ -54,34 +62,24 @@
*
* @hide
*/
-public class TouchExplorer implements Explorer {
+public class TouchExplorer {
+
private static final boolean DEBUG = false;
// Tag for logging received events.
- private static final String LOG_TAG_RECEIVED = "TouchExplorer-RECEIVED";
- // Tag for logging injected events.
- private static final String LOG_TAG_INJECTED = "TouchExplorer-INJECTED";
- // Tag for logging the current state.
- private static final String LOG_TAG_STATE = "TouchExplorer-STATE";
+ private static final String LOG_TAG = "TouchExplorer";
// States this explorer can be in.
private static final int STATE_TOUCH_EXPLORING = 0x00000001;
private static final int STATE_DRAGGING = 0x00000002;
private static final int STATE_DELEGATING = 0x00000004;
-
- // Invalid pointer ID.
- private static final int INVALID_POINTER_ID = -1;
+ private static final int STATE_GESTURE_DETECTING = 0x00000005;
// The time slop in milliseconds for activating an item after it has
// been touch explored. Tapping on an item within this slop will perform
// a click and tapping and holding down a long press.
private static final long ACTIVATION_TIME_SLOP = 2000;
- // This constant captures the current implementation detail that
- // pointer IDs are between 0 and 31 inclusive (subject to change).
- // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
- private static final int MAX_POINTER_COUNT = 32;
-
// The minimum of the cosine between the vectors of two moving
// pointers so they can be considered moving in the same direction.
private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
@@ -92,6 +90,14 @@
// Constant referring to the ids bits of all pointers.
private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
+ // This constant captures the current implementation detail that
+ // pointer IDs are between 0 and 31 inclusive (subject to change).
+ // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ public static final int MAX_POINTER_COUNT = 32;
+
+ // Invalid pointer ID.
+ public static final int INVALID_POINTER_ID = -1;
+
// Temporary array for storing pointer IDs.
private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
@@ -103,10 +109,6 @@
// which delegates event processing to this touch explorer.
private final InputFilter mInputFilter;
- // Helper class for tracking pointers on the screen, for example which
- // pointers are down, which are active, etc.
- private final PointerTracker mPointerTracker;
-
// Handle to the accessibility manager for firing accessibility events
// announcing touch exploration gesture start and end.
private final AccessibilityManager mAccessibilityManager;
@@ -132,21 +134,48 @@
// Command for delayed sending of a long press.
private final PerformLongPressDelayed mPerformLongPressDelayed;
+ private VelocityTracker mVelocityTracker;
+
+ private final ReceivedPointerTracker mReceivedPointerTracker;
+
+ private final InjectedPointerTracker mInjectedPointerTracker;
+
+ private final GestureListener mGestureListener;
+
+ /**
+ * Callback for gesture detection.
+ */
+ public interface GestureListener {
+
+ /**
+ * Called when a given gesture was performed.
+ *
+ * @param gestureId The gesture id.
+ */
+ public void onGesture(int gestureId);
+ }
+
/**
* Creates a new instance.
*
* @param inputFilter The input filter associated with this explorer.
* @param context A context handle for accessing resources.
*/
- public TouchExplorer(InputFilter inputFilter, Context context) {
+ public TouchExplorer(InputFilter inputFilter, Context context,
+ GestureListener gestureListener) {
+ mGestureListener = gestureListener;
+ mReceivedPointerTracker = new ReceivedPointerTracker(context);
+ mInjectedPointerTracker = new InjectedPointerTracker();
mInputFilter = inputFilter;
mTouchExplorationTapSlop =
- ViewConfiguration.get(context).getScaledTouchExplorationTapSlop();
- mPointerTracker = new PointerTracker(context);
+ ViewConfiguration.get(context).getScaledTouchExploreTapSlop();
mHandler = new Handler(context.getMainLooper());
mSendHoverDelayed = new SendHoverDelayed();
mPerformLongPressDelayed = new PerformLongPressDelayed();
mAccessibilityManager = AccessibilityManager.getInstance(context);
+ mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
+ mGestureLibrary.setOrientationStyle(4);
+ mGestureLibrary.load();
}
public void clear(MotionEvent event, int policyFlags) {
@@ -154,18 +183,14 @@
clear();
}
- /**
- * {@inheritDoc}
- */
public void onMotionEvent(MotionEvent event, int policyFlags) {
if (DEBUG) {
- Slog.d(LOG_TAG_RECEIVED, "Received event: " + event + ", policyFlags=0x"
+ Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
- Slog.d(LOG_TAG_STATE, getStateSymbolicName(mCurrentState));
+ Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
}
- // Keep track of the pointers's state.
- mPointerTracker.onReceivedMotionEvent(event);
+ mReceivedPointerTracker.onMotionEvent(event);
switch(mCurrentState) {
case STATE_TOUCH_EXPLORING: {
@@ -177,9 +202,11 @@
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, policyFlags);
} break;
- default: {
+ case STATE_GESTURE_DETECTING: {
+ handleMotionEventGestureDetecting(event, policyFlags);
+ } break;
+ default:
throw new IllegalStateException("Illegal state: " + mCurrentState);
- }
}
}
@@ -190,8 +217,14 @@
* @param policyFlags The policy flags associated with the event.
*/
private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
- PointerTracker pointerTracker = mPointerTracker;
- final int activePointerCount = pointerTracker.getActivePointerCount();
+ ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
+ InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
+ final int activePointerCount = receivedTracker.getActivePointerCount();
+
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
@@ -205,9 +238,9 @@
mSendHoverDelayed.remove();
mPerformLongPressDelayed.remove();
// Send a hover for every finger down so the user gets feedback.
- final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerId = receivedTracker.getPrimaryActivePointerId();
final int pointerIdBits = (1 << pointerId);
- final int lastAction = pointerTracker.getLastInjectedHoverAction();
+ final int lastAction = injectedTracker.getLastInjectedHoverAction();
// Deliver hover enter with a delay to have a change to detect
// whether the user actually starts a scrolling gesture.
@@ -232,7 +265,7 @@
// If the down is in the time slop => schedule a long press.
final long pointerDownTime =
- pointerTracker.getReceivedPointerDownTime(pointerId);
+ receivedTracker.getReceivedPointerDownTime(pointerId);
final long lastExploreTime = mLastTouchExploreEvent.getEventTime();
final long deltaTimeExplore = pointerDownTime - lastExploreTime;
if (deltaTimeExplore <= ACTIVATION_TIME_SLOP) {
@@ -247,7 +280,7 @@
}
} break;
case MotionEvent.ACTION_MOVE: {
- final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerId = receivedTracker.getPrimaryActivePointerId();
final int pointerIndex = event.findPointerIndex(pointerId);
final int pointerIdBits = (1 << pointerId);
switch (activePointerCount) {
@@ -258,13 +291,27 @@
// Detect touch exploration gesture start by having one active pointer
// that moved more than a given distance.
if (!mTouchExploreGestureInProgress) {
- final float deltaX = pointerTracker.getReceivedPointerDownX(pointerId)
+ final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- event.getX(pointerIndex);
- final float deltaY = pointerTracker.getReceivedPointerDownY(pointerId)
+ final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- event.getY(pointerIndex);
final double moveDelta = Math.hypot(deltaX, deltaY);
if (moveDelta > mTouchExplorationTapSlop) {
+
+ mVelocityTracker.computeCurrentVelocity(1000);
+ final float maxAbsVelocity = Math.max(
+ Math.abs(mVelocityTracker.getXVelocity(pointerId)),
+ Math.abs(mVelocityTracker.getYVelocity(pointerId)));
+ // TODO: Tune the velocity cut off and add a constant.
+ if (maxAbsVelocity > 1000) {
+ clear(event, policyFlags);
+ mCurrentState = STATE_GESTURE_DETECTING;
+ event.setAction(MotionEvent.ACTION_DOWN);
+ handleMotionEventGestureDetecting(event, policyFlags);
+ return;
+ }
+
mTouchExploreGestureInProgress = true;
sendAccessibilityEvent(TYPE_TOUCH_EXPLORATION_GESTURE_START);
// Make sure the scheduled down/move event is sent.
@@ -272,7 +319,7 @@
mPerformLongPressDelayed.remove();
// If we have transitioned to exploring state from another one
// we need to send a hover enter event here.
- final int lastAction = mPointerTracker.getLastInjectedHoverAction();
+ final int lastAction = injectedTracker.getLastInjectedHoverAction();
if (lastAction == MotionEvent.ACTION_HOVER_EXIT) {
sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER,
pointerIdBits, policyFlags);
@@ -355,12 +402,12 @@
} break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP: {
- final int pointerId = pointerTracker.getLastReceivedUpPointerId();
+ final int pointerId = receivedTracker.getLastReceivedUpPointerId();
final int pointerIdBits = (1 << pointerId);
switch (activePointerCount) {
case 0: {
// If the pointer that went up was not active we have nothing to do.
- if (!pointerTracker.wasLastReceivedUpPointerActive()) {
+ if (!receivedTracker.wasLastReceivedUpPointerActive()) {
break;
}
@@ -381,7 +428,7 @@
if (mLastTouchExploreEvent != null) {
// If the down was not in the time slop => nothing else to do.
final long eventTime =
- pointerTracker.getLastReceivedUpPointerDownTime();
+ receivedTracker.getLastReceivedUpPointerDownTime();
final long exploreTime = mLastTouchExploreEvent.getEventTime();
final long deltaTime = eventTime - exploreTime;
if (deltaTime > ACTIVATION_TIME_SLOP) {
@@ -422,14 +469,22 @@
}
} break;
}
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ mVelocityTracker = null;
+ }
} break;
case MotionEvent.ACTION_CANCEL: {
mSendHoverDelayed.remove();
mPerformLongPressDelayed.remove();
- final int pointerId = pointerTracker.getPrimaryActivePointerId();
+ final int pointerId = receivedTracker.getPrimaryActivePointerId();
final int pointerIdBits = (1 << pointerId);
ensureHoverExitSent(event, pointerIdBits, policyFlags);
clear();
+ if (mVelocityTracker != null) {
+ mVelocityTracker.clear();
+ mVelocityTracker = null;
+ }
} break;
}
}
@@ -455,7 +510,7 @@
sendDownForAllActiveNotInjectedPointers(event, policyFlags);
} break;
case MotionEvent.ACTION_MOVE: {
- final int activePointerCount = mPointerTracker.getActivePointerCount();
+ final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
switch (activePointerCount) {
case 1: {
// do nothing
@@ -487,7 +542,7 @@
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
- final int activePointerCount = mPointerTracker.getActivePointerCount();
+ final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
switch (activePointerCount) {
case 1: {
// Send an event to the end of the drag gesture.
@@ -525,7 +580,8 @@
case MotionEvent.ACTION_MOVE: {
// Check whether some other pointer became active because they have moved
// a given distance and if such exist send them to the view hierarchy
- final int notInjectedCount = mPointerTracker.getNotInjectedActivePointerCount();
+ final int notInjectedCount = getNotInjectedActivePointerCount(
+ mReceivedPointerTracker, mInjectedPointerTracker);
if (notInjectedCount > 0) {
MotionEvent prototype = MotionEvent.obtain(event);
sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
@@ -533,7 +589,7 @@
} break;
case MotionEvent.ACTION_POINTER_UP: {
// No active pointers => go to initial state.
- if (mPointerTracker.getActivePointerCount() == 0) {
+ if (mReceivedPointerTracker.getActivePointerCount() == 0) {
mCurrentState = STATE_TOUCH_EXPLORING;
}
} break;
@@ -545,6 +601,72 @@
sendMotionEventStripInactivePointers(event, policyFlags);
}
+ private float mPreviousX;
+ private float mPreviousY;
+
+ private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+
+ private static final int TOUCH_TOLERANCE = 3;
+ private static final float MIN_PREDICTION_SCORE = 2.0f;
+
+ private GestureLibrary mGestureLibrary;
+
+ private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ final float x = event.getX();
+ final float y = event.getY();
+ mPreviousX = x;
+ mPreviousY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ final float x = event.getX();
+ final float y = event.getY();
+ final float dX = Math.abs(x - mPreviousX);
+ final float dY = Math.abs(y - mPreviousY);
+ if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
+ mPreviousX = x;
+ mPreviousY = y;
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ }
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ float x = event.getX();
+ float y = event.getY();
+ mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+
+ Gesture gesture = new Gesture();
+ gesture.addStroke(new GestureStroke(mStrokeBuffer));
+
+ ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
+ if (!predictions.isEmpty()) {
+ Prediction bestPrediction = predictions.get(0);
+ if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
+ + bestPrediction.score);
+ }
+ try {
+ final int gestureId = Integer.parseInt(bestPrediction.name);
+ mGestureListener.onGesture(gestureId);
+ } catch (NumberFormatException nfe) {
+ Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
+ }
+ }
+ }
+
+ mStrokeBuffer.clear();
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ } break;
+ case MotionEvent.ACTION_CANCEL: {
+ mStrokeBuffer.clear();
+ mCurrentState = STATE_TOUCH_EXPLORING;
+ } break;
+ }
+ }
+
/**
* Sends down events to the view hierarchy for all active pointers which are
* not already being delivered i.e. pointers that are not yet injected.
@@ -553,14 +675,15 @@
* @param policyFlags The policy flags associated with the event.
*/
private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
- final PointerTracker pointerTracker = mPointerTracker;
+ ReceivedPointerTracker receivedPointers = mReceivedPointerTracker;
+ InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
int pointerIdBits = 0;
final int pointerCount = prototype.getPointerCount();
// Find which pointers are already injected.
for (int i = 0; i < pointerCount; i++) {
final int pointerId = prototype.getPointerId(i);
- if (pointerTracker.isInjectedPointerDown(pointerId)) {
+ if (injectedPointers.isInjectedPointerDown(pointerId)) {
pointerIdBits |= (1 << pointerId);
}
}
@@ -569,11 +692,11 @@
for (int i = 0; i < pointerCount; i++) {
final int pointerId = prototype.getPointerId(i);
// Skip inactive pointers.
- if (!pointerTracker.isActivePointer(pointerId)) {
+ if (!receivedPointers.isActivePointer(pointerId)) {
continue;
}
// Do not send event for already delivered pointers.
- if (pointerTracker.isInjectedPointerDown(pointerId)) {
+ if (injectedPointers.isInjectedPointerDown(pointerId)) {
continue;
}
pointerIdBits |= (1 << pointerId);
@@ -590,7 +713,7 @@
* @param policyFlags The policy flags associated with the event.
*/
private void ensureHoverExitSent(MotionEvent prototype, int pointerIdBits, int policyFlags) {
- final int lastAction = mPointerTracker.getLastInjectedHoverAction();
+ final int lastAction = mInjectedPointerTracker.getLastInjectedHoverAction();
if (lastAction != MotionEvent.ACTION_HOVER_EXIT) {
sendMotionEvent(prototype, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits,
policyFlags);
@@ -605,13 +728,13 @@
* @param policyFlags The policy flags associated with the event.
*/
private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
- final PointerTracker pointerTracker = mPointerTracker;
+ final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
int pointerIdBits = 0;
final int pointerCount = prototype.getPointerCount();
for (int i = 0; i < pointerCount; i++) {
final int pointerId = prototype.getPointerId(i);
// Skip non injected down pointers.
- if (!pointerTracker.isInjectedPointerDown(pointerId)) {
+ if (!injectedTracked.isInjectedPointerDown(pointerId)) {
continue;
}
pointerIdBits |= (1 << pointerId);
@@ -627,18 +750,18 @@
* @param policyFlags The policy flags associated with the event.
*/
private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
- PointerTracker pointerTracker = mPointerTracker;
+ ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
// All pointers active therefore we just inject the event as is.
- if (prototype.getPointerCount() == pointerTracker.getActivePointerCount()) {
+ if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) {
sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
return;
}
// No active pointers and the one that just went up was not
// active, therefore we have nothing to do.
- if (pointerTracker.getActivePointerCount() == 0
- && !pointerTracker.wasLastReceivedUpPointerActive()) {
+ if (receivedTracker.getActivePointerCount() == 0
+ && !receivedTracker.wasLastReceivedUpPointerActive()) {
return;
}
@@ -647,7 +770,7 @@
final int actionMasked = prototype.getActionMasked();
final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
if (actionMasked != MotionEvent.ACTION_MOVE) {
- if (!pointerTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
+ if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
return;
}
}
@@ -658,7 +781,7 @@
final int pointerCount = prototype.getPointerCount();
for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
final int pointerId = prototype.getPointerId(pointerIndex);
- if (pointerTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
+ if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
pointerIdBits |= (1 << pointerId);
}
}
@@ -700,19 +823,20 @@
if (action == MotionEvent.ACTION_DOWN) {
event.setDownTime(event.getEventTime());
} else {
- event.setDownTime(mPointerTracker.getLastInjectedDownEventTime());
+ event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
}
if (DEBUG) {
- Slog.d(LOG_TAG_INJECTED, "Injecting event: " + event + ", policyFlags=0x"
+ Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
+ Integer.toHexString(policyFlags));
}
// Make sure that the user will see the event.
policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
- mPointerTracker.onInjectedMotionEvent(event);
mInputFilter.sendInputEvent(event, policyFlags);
+ mInjectedPointerTracker.onMotionEvent(event);
+
if (event != prototype) {
event.recycle();
}
@@ -730,9 +854,9 @@
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
- PointerTracker pointerTracker = mPointerTracker;
+ InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
// Compute the action based on how many down pointers are injected.
- if (pointerTracker.getInjectedPointerDownCount() == 0) {
+ if (injectedTracker.getInjectedPointerDownCount() == 0) {
return MotionEvent.ACTION_DOWN;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
@@ -740,9 +864,9 @@
}
}
case MotionEvent.ACTION_POINTER_UP: {
- PointerTracker pointerTracker = mPointerTracker;
+ InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
// Compute the action based on how many down pointers are injected.
- if (pointerTracker.getInjectedPointerDownCount() == 1) {
+ if (injectedTracker.getInjectedPointerDownCount() == 1) {
return MotionEvent.ACTION_UP;
} else {
return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
@@ -761,9 +885,9 @@
* @return True if the gesture is a dragging one.
*/
private boolean isDraggingGesture(MotionEvent event) {
- PointerTracker pointerTracker = mPointerTracker;
+ ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
int[] pointerIds = mTempPointerIds;
- pointerTracker.populateActivePointerIds(pointerIds);
+ receivedTracker.populateActivePointerIds(pointerIds);
final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
@@ -775,9 +899,9 @@
// Check if the pointers are moving in the same direction.
final float firstDeltaX =
- firstPtrX - pointerTracker.getReceivedPointerDownX(firstPtrIndex);
+ firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex);
final float firstDeltaY =
- firstPtrY - pointerTracker.getReceivedPointerDownY(firstPtrIndex);
+ firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex);
if (firstDeltaX == 0 && firstDeltaY == 0) {
return true;
@@ -791,9 +915,9 @@
(firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
final float secondDeltaX =
- secondPtrX - pointerTracker.getReceivedPointerDownX(secondPtrIndex);
+ secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex);
final float secondDeltaY =
- secondPtrY - pointerTracker.getReceivedPointerDownY(secondPtrIndex);
+ secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex);
if (secondDeltaX == 0 && secondDeltaY == 0) {
return true;
@@ -832,7 +956,8 @@
public void clear() {
mSendHoverDelayed.remove();
mPerformLongPressDelayed.remove();
- mPointerTracker.clear();
+ mReceivedPointerTracker.clear();
+ mInjectedPointerTracker.clear();
mLastTouchExploreEvent = null;
mCurrentState = STATE_TOUCH_EXPLORING;
mTouchExploreGestureInProgress = false;
@@ -853,27 +978,253 @@
return "STATE_DRAGGING";
case STATE_DELEGATING:
return "STATE_DELEGATING";
+ case STATE_GESTURE_DETECTING:
+ return "STATE_GESTURE_DETECTING";
default:
throw new IllegalArgumentException("Unknown state: " + state);
}
}
/**
- * Helper class for tracking pointers and more specifically which of
- * them are currently down, which are active, and which are delivered
- * to the view hierarchy. The enclosing {@link TouchExplorer} uses the
- * pointer state reported by this class to perform touch exploration.
- * <p>
- * The main purpose of this class is to allow the touch explorer to
- * disregard pointers put down by accident by the user and not being
- * involved in the interaction. For example, a blind user grabs the
- * device with her left hand such that she touches the screen and she
- * uses her right hand's index finger to explore the screen content.
- * In this scenario the touches generated by the left hand are to be
- * ignored.
+ * @return The number of non injected active pointers.
*/
- class PointerTracker {
- private static final String LOG_TAG = "PointerTracker";
+ private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker,
+ InjectedPointerTracker injectedTracker) {
+ final int pointerState = receivedTracker.getActivePointers()
+ & ~injectedTracker.getInjectedPointersDown();
+ return Integer.bitCount(pointerState);
+ }
+
+ /**
+ * Class for delayed sending of long press.
+ */
+ private final class PerformLongPressDelayed implements Runnable {
+ private MotionEvent mEvent;
+ private int mPolicyFlags;
+
+ public void post(MotionEvent prototype, int policyFlags, long delay) {
+ mEvent = MotionEvent.obtain(prototype);
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ }
+
+ public void remove() {
+ if (isPenidng()) {
+ mHandler.removeCallbacks(this);
+ clear();
+ }
+ }
+
+ private boolean isPenidng() {
+ return (mEvent != null);
+ }
+
+ @Override
+ public void run() {
+ mCurrentState = STATE_DELEGATING;
+ // Make sure the scheduled hover exit is delivered.
+ mSendHoverDelayed.remove();
+ final int pointerId = mReceivedPointerTracker.getPrimaryActivePointerId();
+ final int pointerIdBits = (1 << pointerId);
+ ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
+
+ sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
+ mTouchExploreGestureInProgress = false;
+ mLastTouchExploreEvent = null;
+ clear();
+ }
+
+ private void clear() {
+ if (!isPenidng()) {
+ return;
+ }
+ mEvent.recycle();
+ mEvent = null;
+ mPolicyFlags = 0;
+ }
+ }
+
+ /**
+ * Class for delayed sending of hover events.
+ */
+ private final class SendHoverDelayed implements Runnable {
+ private MotionEvent mEvent;
+ private int mAction;
+ private int mPointerIdBits;
+ private int mPolicyFlags;
+
+ public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
+ long delay) {
+ remove();
+ mEvent = MotionEvent.obtain(prototype);
+ mAction = action;
+ mPointerIdBits = pointerIdBits;
+ mPolicyFlags = policyFlags;
+ mHandler.postDelayed(this, delay);
+ }
+
+ public void remove() {
+ mHandler.removeCallbacks(this);
+ clear();
+ }
+
+ private boolean isPenidng() {
+ return (mEvent != null);
+ }
+
+ private void clear() {
+ if (!isPenidng()) {
+ return;
+ }
+ mEvent.recycle();
+ mEvent = null;
+ mAction = 0;
+ mPointerIdBits = -1;
+ mPolicyFlags = 0;
+ }
+
+ public void forceSendAndRemove() {
+ if (isPenidng()) {
+ run();
+ remove();
+ }
+ }
+
+ public void run() {
+ if (DEBUG) {
+ if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
+ Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
+ } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
+ } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
+ Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
+ }
+ }
+
+ sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
+ clear();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return LOG_TAG;
+ }
+
+ class InjectedPointerTracker {
+ private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
+
+ // Keep track of which pointers sent to the system are down.
+ private int mInjectedPointersDown;
+
+ // The time of the last injected down.
+ private long mLastInjectedDownEventTime;
+
+ // The action of the last injected hover event.
+ private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+
+ /**
+ * Processes an injected {@link MotionEvent} event.
+ *
+ * @param event The event to process.
+ */
+ public void onMotionEvent(MotionEvent event) {
+ final int action = event.getActionMasked();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ final int pointerId = event.getPointerId(event.getActionIndex());
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown |= pointerFlag;
+ mLastInjectedDownEventTime = event.getDownTime();
+ } break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int pointerId = event.getPointerId(event.getActionIndex());
+ final int pointerFlag = (1 << pointerId);
+ mInjectedPointersDown &= ~pointerFlag;
+ if (mInjectedPointersDown == 0) {
+ mLastInjectedDownEventTime = 0;
+ }
+ } break;
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE:
+ case MotionEvent.ACTION_HOVER_EXIT: {
+ mLastInjectedHoverEventAction = event.getActionMasked();
+ } break;
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer: " + toString());
+ }
+ }
+
+ /**
+ * Clears the internals state.
+ */
+ public void clear() {
+ mInjectedPointersDown = 0;
+ }
+
+ /**
+ * @return The time of the last injected down event.
+ */
+ public long getLastInjectedDownEventTime() {
+ return mLastInjectedDownEventTime;
+ }
+
+ /**
+ * @return The number of down pointers injected to the view hierarchy.
+ */
+ public int getInjectedPointerDownCount() {
+ return Integer.bitCount(mInjectedPointersDown);
+ }
+
+ /**
+ * @return The bits of the injected pointers that are down.
+ */
+ public int getInjectedPointersDown() {
+ return mInjectedPointersDown;
+ }
+
+ /**
+ * Whether an injected pointer is down.
+ *
+ * @param pointerId The unique pointer id.
+ * @return True if the pointer is down.
+ */
+ public boolean isInjectedPointerDown(int pointerId) {
+ final int pointerFlag = (1 << pointerId);
+ return (mInjectedPointersDown & pointerFlag) != 0;
+ }
+
+ /**
+ * @return The action of the last injected hover event.
+ */
+ public int getLastInjectedHoverAction() {
+ return mLastInjectedHoverEventAction;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("=========================");
+ builder.append("\nDown pointers #");
+ builder.append(Integer.bitCount(mInjectedPointersDown));
+ builder.append(" [ ");
+ for (int i = 0; i < MAX_POINTER_COUNT; i++) {
+ if ((mInjectedPointersDown & i) != 0) {
+ builder.append(i);
+ builder.append(" ");
+ }
+ }
+ builder.append("]");
+ builder.append("\n=========================");
+ return builder.toString();
+ }
+ }
+
+ class ReceivedPointerTracker {
+ private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
// The coefficient by which to multiply
// ViewConfiguration.#getScaledTouchSlop()
@@ -902,26 +1253,19 @@
// Flag indicating that there is at least one active pointer moving.
private boolean mHasMovingActivePointer;
- // Keep track of which pointers sent to the system are down.
- private int mInjectedPointersDown;
-
// Keep track of the last up pointer data.
private long mLastReceivedUpPointerDownTime;
private int mLastReceivedUpPointerId;
private boolean mLastReceivedUpPointerActive;
-
- // The time of the last injected down.
- private long mLastInjectedDownEventTime;
-
- // The action of the last injected hover event.
- private int mLastInjectedHoverEventAction = MotionEvent.ACTION_HOVER_EXIT;
+ private float mLastReceivedUpPointerDownX;
+ private float mLastReceivedUpPointerDownY;
/**
* Creates a new instance.
*
* @param context Context for looking up resources.
*/
- public PointerTracker(Context context) {
+ public ReceivedPointerTracker(Context context) {
mThresholdActivePointer =
ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
}
@@ -937,10 +1281,11 @@
mActivePointers = 0;
mPrimaryActivePointerId = 0;
mHasMovingActivePointer = false;
- mInjectedPointersDown = 0;
mLastReceivedUpPointerDownTime = 0;
mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerActive = false;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReceivedUpPointerDownY = 0;
}
/**
@@ -948,12 +1293,10 @@
*
* @param event The event to process.
*/
- public void onReceivedMotionEvent(MotionEvent event) {
+ public void onMotionEvent(MotionEvent event) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_DOWN: {
- // New gesture so restart tracking injected down pointers.
- mInjectedPointersDown = 0;
handleReceivedPointerDown(event.getActionIndex(), event);
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
@@ -970,39 +1313,7 @@
} break;
}
if (DEBUG) {
- Slog.i(LOG_TAG, "Received pointer: " + toString());
- }
- }
-
- /**
- * Processes an injected {@link MotionEvent} event.
- *
- * @param event The event to process.
- */
- public void onInjectedMotionEvent(MotionEvent event) {
- final int action = event.getActionMasked();
- switch (action) {
- case MotionEvent.ACTION_DOWN: {
- handleInjectedPointerDown(event.getActionIndex(), event);
- mLastInjectedDownEventTime = event.getDownTime();
- } break;
- case MotionEvent.ACTION_POINTER_DOWN: {
- handleInjectedPointerDown(event.getActionIndex(), event);
- } break;
- case MotionEvent.ACTION_UP: {
- handleInjectedPointerUp(event.getActionIndex(), event);
- } break;
- case MotionEvent.ACTION_POINTER_UP: {
- handleInjectedPointerUp(event.getActionIndex(), event);
- } break;
- case MotionEvent.ACTION_HOVER_ENTER:
- case MotionEvent.ACTION_HOVER_MOVE:
- case MotionEvent.ACTION_HOVER_EXIT: {
- mLastInjectedHoverEventAction = event.getActionMasked();
- } break;
- }
- if (DEBUG) {
- Slog.i(LOG_TAG, "Injected pointer: " + toString());
+ Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
}
}
@@ -1014,6 +1325,13 @@
}
/**
+ * @return The bits of the pointers that are active.
+ */
+ public int getActivePointers() {
+ return mActivePointers;
+ }
+
+ /**
* @return The number of down input pointers that are active.
*/
public int getActivePointerCount() {
@@ -1032,24 +1350,6 @@
}
/**
- * Whether an injected pointer is down.
- *
- * @param pointerId The unique pointer id.
- * @return True if the pointer is down.
- */
- public boolean isInjectedPointerDown(int pointerId) {
- final int pointerFlag = (1 << pointerId);
- return (mInjectedPointersDown & pointerFlag) != 0;
- }
-
- /**
- * @return The number of down pointers injected to the view hierarchy.
- */
- public int getInjectedPointerDownCount() {
- return Integer.bitCount(mInjectedPointersDown);
- }
-
- /**
* Whether an input pointer is active.
*
* @param pointerId The unique pointer id.
@@ -1108,27 +1408,27 @@
return mLastReceivedUpPointerId;
}
+
+ /**
+ * @return The down X of the last received pointer that went up.
+ */
+ public float getLastReceivedUpPointerDownX() {
+ return mLastReceivedUpPointerDownX;
+ }
+
+ /**
+ * @return The down Y of the last received pointer that went up.
+ */
+ public float getLastReceivedUpPointerDownY() {
+ return mLastReceivedUpPointerDownY;
+ }
+
/**
* @return Whether the last received pointer that went up was active.
*/
public boolean wasLastReceivedUpPointerActive() {
return mLastReceivedUpPointerActive;
}
-
- /**
- * @return The time of the last injected down event.
- */
- public long getLastInjectedDownEventTime() {
- return mLastInjectedDownEventTime;
- }
-
- /**
- * @return The action of the last injected hover event.
- */
- public int getLastInjectedHoverAction() {
- return mLastInjectedHoverEventAction;
- }
-
/**
* Populates the active pointer IDs to the given array.
* <p>
@@ -1147,18 +1447,10 @@
}
/**
- * @return The number of non injected active pointers.
- */
- public int getNotInjectedActivePointerCount() {
- final int pointerState = mActivePointers & ~mInjectedPointersDown;
- return Integer.bitCount(pointerState);
- }
-
- /**
* @param pointerId The unique pointer id.
* @return Whether the pointer is active or was the last active than went up.
*/
- private boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
+ public boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
return (isActivePointer(pointerId)
|| (mLastReceivedUpPointerId == pointerId
&& mLastReceivedUpPointerActive));
@@ -1177,6 +1469,8 @@
mLastReceivedUpPointerId = 0;
mLastReceivedUpPointerDownTime = 0;
mLastReceivedUpPointerActive = false;
+ mLastReceivedUpPointerDownX = 0;
+ mLastReceivedUpPointerDownX = 0;
mReceivedPointersDown |= pointerFlag;
mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
@@ -1217,6 +1511,8 @@
mLastReceivedUpPointerId = pointerId;
mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
mLastReceivedUpPointerActive = isActivePointer(pointerId);
+ mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
+ mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
mReceivedPointersDown &= ~pointerFlag;
mActivePointers &= ~pointerFlag;
@@ -1233,33 +1529,6 @@
}
/**
- * Handles a injected pointer down event.
- *
- * @param pointerIndex The index of the pointer that has changed.
- * @param event The event to be handled.
- */
- private void handleInjectedPointerDown(int pointerIndex, MotionEvent event) {
- final int pointerId = event.getPointerId(pointerIndex);
- final int pointerFlag = (1 << pointerId);
- mInjectedPointersDown |= pointerFlag;
- }
-
- /**
- * Handles a injected pointer up event.
- *
- * @param pointerIndex The index of the pointer that has changed.
- * @param event The event to be handled.
- */
- private void handleInjectedPointerUp(int pointerIndex, MotionEvent event) {
- final int pointerId = event.getPointerId(pointerIndex);
- final int pointerFlag = (1 << pointerId);
- mInjectedPointersDown &= ~pointerFlag;
- if (mInjectedPointersDown == 0) {
- mLastInjectedDownEventTime = 0;
- }
- }
-
- /**
* Detects the active pointers in an event.
*
* @param event The event to examine.
@@ -1348,117 +1617,4 @@
return builder.toString();
}
}
-
- /**
- * Class for delayed sending of long press.
- */
- private final class PerformLongPressDelayed implements Runnable {
- private MotionEvent mEvent;
- private int mPolicyFlags;
-
- public void post(MotionEvent prototype, int policyFlags, long delay) {
- mEvent = MotionEvent.obtain(prototype);
- mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
- }
-
- public void remove() {
- if (isPenidng()) {
- mHandler.removeCallbacks(this);
- clear();
- }
- }
-
- private boolean isPenidng() {
- return (mEvent != null);
- }
-
- @Override
- public void run() {
- mCurrentState = STATE_DELEGATING;
- // Make sure the scheduled hover exit is delivered.
- mSendHoverDelayed.remove();
- final int pointerId = mPointerTracker.getPrimaryActivePointerId();
- final int pointerIdBits = (1 << pointerId);
- ensureHoverExitSent(mEvent, pointerIdBits, mPolicyFlags);
-
- sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
- mTouchExploreGestureInProgress = false;
- mLastTouchExploreEvent = null;
- clear();
- }
-
- private void clear() {
- if (!isPenidng()) {
- return;
- }
- mEvent.recycle();
- mEvent = null;
- mPolicyFlags = 0;
- }
- }
-
- /**
- * Class for delayed sending of hover events.
- */
- private final class SendHoverDelayed implements Runnable {
- private static final String LOG_TAG = "SendHoverEnterOrExitDelayed";
-
- private MotionEvent mEvent;
- private int mAction;
- private int mPointerIdBits;
- private int mPolicyFlags;
-
- public void post(MotionEvent prototype, int action, int pointerIdBits, int policyFlags,
- long delay) {
- remove();
- mEvent = MotionEvent.obtain(prototype);
- mAction = action;
- mPointerIdBits = pointerIdBits;
- mPolicyFlags = policyFlags;
- mHandler.postDelayed(this, delay);
- }
-
- public void remove() {
- mHandler.removeCallbacks(this);
- clear();
- }
-
- private boolean isPenidng() {
- return (mEvent != null);
- }
-
- private void clear() {
- if (!isPenidng()) {
- return;
- }
- mEvent.recycle();
- mEvent = null;
- mAction = 0;
- mPointerIdBits = -1;
- mPolicyFlags = 0;
- }
-
- public void forceSendAndRemove() {
- if (isPenidng()) {
- run();
- remove();
- }
- }
-
- public void run() {
- if (DEBUG) {
- if (mAction == MotionEvent.ACTION_HOVER_ENTER) {
- Slog.d(LOG_TAG, "Injecting: " + MotionEvent.ACTION_HOVER_ENTER);
- } else if (mAction == MotionEvent.ACTION_HOVER_MOVE) {
- Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_MOVE");
- } else if (mAction == MotionEvent.ACTION_HOVER_EXIT) {
- Slog.d(LOG_TAG, "Injecting: MotionEvent.ACTION_HOVER_EXIT");
- }
- }
-
- sendMotionEvent(mEvent, mAction, mPointerIdBits, mPolicyFlags);
- clear();
- }
- }
}
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 78b441a..e37adc7 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -697,6 +697,16 @@
boolean mSleeping = false;
/**
+ * State of external calls telling us if the device is asleep.
+ */
+ boolean mWentToSleep = false;
+
+ /**
+ * State of external call telling us if the lock screen is shown.
+ */
+ boolean mLockScreenShown = false;
+
+ /**
* Set if we are shutting down the system, similar to sleeping.
*/
boolean mShuttingDown = false;
@@ -6656,17 +6666,26 @@
}
public void goingToSleep() {
+ if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.DEVICE_POWER);
+ }
+
synchronized(this) {
- mSleeping = true;
+ mWentToSleep = true;
mWindowManager.setEventDispatching(false);
- mMainStack.stopIfSleepingLocked();
+ if (!mSleeping) {
+ mSleeping = true;
+ mMainStack.stopIfSleepingLocked();
- // Initialize the wake times of all processes.
- checkExcessivePowerUsageLocked(false);
- mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
- Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
- mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+ // Initialize the wake times of all processes.
+ checkExcessivePowerUsageLocked(false);
+ mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+ Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+ mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+ }
}
}
@@ -6726,12 +6745,40 @@
Binder.restoreCallingIdentity(origId);
}
+ private void comeOutOfSleepIfNeededLocked() {
+ if (!mWentToSleep && !mLockScreenShown) {
+ if (mSleeping) {
+ mSleeping = false;
+ mMainStack.awakeFromSleepingLocked();
+ mMainStack.resumeTopActivityLocked(null);
+ }
+ }
+ }
+
public void wakingUp() {
+ if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.DEVICE_POWER);
+ }
+
synchronized(this) {
+ mWentToSleep = false;
mWindowManager.setEventDispatching(true);
- mSleeping = false;
- mMainStack.awakeFromSleepingLocked();
- mMainStack.resumeTopActivityLocked(null);
+ comeOutOfSleepIfNeededLocked();
+ }
+ }
+
+ public void setLockScreenShown(boolean shown) {
+ if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.DEVICE_POWER);
+ }
+
+ synchronized(this) {
+ mLockScreenShown = shown;
+ comeOutOfSleepIfNeededLocked();
}
}
@@ -8309,7 +8356,7 @@
+ android.Manifest.permission.DUMP);
return;
}
-
+
boolean dumpAll = false;
boolean dumpClient = false;
String dumpPackage = null;
@@ -8352,7 +8399,9 @@
pw.println("Unknown argument: " + opt + "; use -h for help");
}
}
-
+
+ long origId = Binder.clearCallingIdentity();
+ boolean more = false;
// Is the caller requesting to dump a particular piece of data?
if (opti < args.length) {
String cmd = args[opti];
@@ -8361,7 +8410,6 @@
synchronized (this) {
dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient, null);
}
- return;
} else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
String[] newArgs;
String name;
@@ -8378,7 +8426,6 @@
synchronized (this) {
dumpBroadcastsLocked(fd, pw, args, opti, true, name);
}
- return;
} else if ("intents".equals(cmd) || "i".equals(cmd)) {
String[] newArgs;
String name;
@@ -8395,7 +8442,6 @@
synchronized (this) {
dumpPendingIntentsLocked(fd, pw, args, opti, true, name);
}
- return;
} else if ("processes".equals(cmd) || "p".equals(cmd)) {
String[] newArgs;
String name;
@@ -8412,12 +8458,10 @@
synchronized (this) {
dumpProcessesLocked(fd, pw, args, opti, true, name);
}
- return;
} else if ("oom".equals(cmd) || "o".equals(cmd)) {
synchronized (this) {
dumpOomLocked(fd, pw, args, opti, true);
}
- return;
} else if ("provider".equals(cmd)) {
String[] newArgs;
String name;
@@ -8434,12 +8478,10 @@
pw.println("No providers match: " + name);
pw.println("Use -h for help.");
}
- return;
} else if ("providers".equals(cmd) || "prov".equals(cmd)) {
synchronized (this) {
dumpProvidersLocked(fd, pw, args, opti, true, null);
}
- return;
} else if ("service".equals(cmd)) {
String[] newArgs;
String name;
@@ -8457,13 +8499,11 @@
pw.println("No services match: " + name);
pw.println("Use -h for help.");
}
- return;
} else if ("package".equals(cmd)) {
String[] newArgs;
if (opti >= args.length) {
pw.println("package: no package name specified");
pw.println("Use -h for help.");
- return;
} else {
dumpPackage = args[opti];
opti++;
@@ -8472,22 +8512,25 @@
args.length - opti);
args = newArgs;
opti = 0;
+ more = true;
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
synchronized (this) {
dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null);
}
- return;
} else {
// Dumping a single activity?
if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
pw.println("Bad activity command, or no activities match: " + cmd);
pw.println("Use -h for help.");
}
+ }
+ if (!more) {
+ Binder.restoreCallingIdentity(origId);
return;
}
}
-
+
// No piece of data specified, dump everything.
synchronized (this) {
boolean needSep;
@@ -8528,8 +8571,9 @@
}
dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
}
+ Binder.restoreCallingIdentity(origId);
}
-
+
boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)");
@@ -8818,7 +8862,13 @@
}
}
}
- pw.println(" mSleeping=" + mSleeping + " mShuttingDown=" + mShuttingDown);
+ if (mSleeping || mWentToSleep || mLockScreenShown) {
+ pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+ + " mLockScreenShown " + mLockScreenShown);
+ }
+ if (mShuttingDown) {
+ pw.println(" mShuttingDown=" + mShuttingDown);
+ }
if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
|| mOrigWaitForDebugger) {
pw.println(" mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index 2f25df1..e819432 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -34,18 +34,23 @@
import android.content.res.XmlResourceParser;
import android.database.ContentObserver;
import android.hardware.input.IInputManager;
+import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.InputManager;
import android.hardware.input.KeyboardLayout;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
import android.os.MessageQueue;
import android.os.Process;
+import android.os.RemoteException;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.Xml;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -77,12 +82,39 @@
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
+ private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+
// Pointer to native input manager service object.
private final int mPtr;
private final Context mContext;
private final Callbacks mCallbacks;
- private final Handler mHandler;
+ private final InputManagerHandler mHandler;
+
+ // Used to simulate a persistent data store for keyboard layouts.
+ // TODO: Replace with the real thing.
+ private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
+
+ // List of currently registered input devices changed listeners by process id.
+ private Object mInputDevicesLock = new Object();
+ private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock
+ private InputDevice[] mInputDevices = new InputDevice[0];
+ private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners =
+ new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock
+ private final ArrayList<InputDevicesChangedListenerRecord>
+ mTempInputDevicesChangedListenersToNotify =
+ new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+
+ // State for vibrator tokens.
+ private Object mVibratorLock = new Object();
+ private HashMap<IBinder, VibratorToken> mVibratorTokens =
+ new HashMap<IBinder, VibratorToken>();
+ private int mNextVibratorTokenValue;
+
+ // State for the currently installed input filter.
+ final Object mInputFilterLock = new Object();
+ InputFilter mInputFilter; // guarded by mInputFilterLock
+ InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
private static native int nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
@@ -111,13 +143,14 @@
private static native void nativeSetSystemUiVisibility(int ptr, int visibility);
private static native void nativeSetFocusedApplication(int ptr,
InputApplicationHandle application);
- private static native InputDevice nativeGetInputDevice(int ptr, int deviceId);
private static native void nativeGetInputConfiguration(int ptr, Configuration configuration);
- private static native int[] nativeGetInputDeviceIds(int ptr);
private static native boolean nativeTransferTouchFocus(int ptr,
InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(int ptr, int speed);
private static native void nativeSetShowTouches(int ptr, boolean enabled);
+ private static native void nativeVibrate(int ptr, int deviceId, long[] pattern,
+ int repeat, int token);
+ private static native void nativeCancelVibrate(int ptr, int deviceId, int token);
private static native String nativeDump(int ptr);
private static native void nativeMonitor(int ptr);
@@ -145,19 +178,10 @@
/** The key is down but is a virtual key press that is being emulated by the system. */
public static final int KEY_STATE_VIRTUAL = 2;
- // Used to simulate a persistent data store for keyboard layouts.
- // TODO: Replace with the real thing.
- private final HashMap<String, String> mFakeRegistry = new HashMap<String, String>();
-
- // State for the currently installed input filter.
- final Object mInputFilterLock = new Object();
- InputFilter mInputFilter;
- InputFilterHost mInputFilterHost;
-
public InputManagerService(Context context, Callbacks callbacks) {
this.mContext = context;
this.mCallbacks = callbacks;
- this.mHandler = new Handler();
+ this.mHandler = new InputManagerHandler();
Slog.i(TAG, "Initializing input manager");
mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
@@ -396,16 +420,98 @@
*/
@Override // Binder call
public InputDevice getInputDevice(int deviceId) {
- return nativeGetInputDevice(mPtr, deviceId);
+ synchronized (mInputDevicesLock) {
+ final int count = mInputDevices.length;
+ for (int i = 0; i < count; i++) {
+ final InputDevice inputDevice = mInputDevices[i];
+ if (inputDevice.getId() == deviceId) {
+ return inputDevice;
+ }
+ }
+ }
+ return null;
}
-
+
/**
* Gets the ids of all input devices in the system.
* @return The input device ids.
*/
@Override // Binder call
public int[] getInputDeviceIds() {
- return nativeGetInputDeviceIds(mPtr);
+ synchronized (mInputDevicesLock) {
+ final int count = mInputDevices.length;
+ int[] ids = new int[count];
+ for (int i = 0; i < count; i++) {
+ ids[i] = mInputDevices[i].getId();
+ }
+ return ids;
+ }
+ }
+
+ @Override // Binder call
+ public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mInputDevicesLock) {
+ int callingPid = Binder.getCallingPid();
+ if (mInputDevicesChangedListeners.get(callingPid) != null) {
+ throw new SecurityException("The calling process has already "
+ + "registered an InputDevicesChangedListener.");
+ }
+
+ InputDevicesChangedListenerRecord record =
+ new InputDevicesChangedListenerRecord(callingPid, listener);
+ try {
+ IBinder binder = listener.asBinder();
+ binder.linkToDeath(record, 0);
+ } catch (RemoteException ex) {
+ // give up
+ throw new RuntimeException(ex);
+ }
+
+ mInputDevicesChangedListeners.put(callingPid, record);
+ }
+ }
+
+ private void onInputDevicesChangedListenerDied(int pid) {
+ synchronized (mInputDevicesLock) {
+ mInputDevicesChangedListeners.remove(pid);
+ }
+ }
+
+ // Must be called on handler.
+ private void deliverInputDevicesChanged() {
+ mTempInputDevicesChangedListenersToNotify.clear();
+
+ final int numListeners;
+ final int[] deviceIdAndGeneration;
+ synchronized (mInputDevicesLock) {
+ if (!mInputDevicesChangedPending) {
+ return;
+ }
+ mInputDevicesChangedPending = false;
+
+ numListeners = mInputDevicesChangedListeners.size();
+ for (int i = 0; i < numListeners; i++) {
+ mTempInputDevicesChangedListenersToNotify.add(
+ mInputDevicesChangedListeners.valueAt(i));
+ }
+
+ final int numDevices = mInputDevices.length;
+ deviceIdAndGeneration = new int[numDevices * 2];
+ for (int i = 0; i < numDevices; i++) {
+ final InputDevice inputDevice = mInputDevices[i];
+ deviceIdAndGeneration[i * 2] = inputDevice.getId();
+ deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+ }
+ }
+
+ for (int i = 0; i < numListeners; i++) {
+ mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
+ deviceIdAndGeneration);
+ }
}
@Override // Binder call
@@ -695,6 +801,65 @@
return result;
}
+ // Binder call
+ @Override
+ public void vibrate(int deviceId, long[] pattern, int repeat, IBinder token) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ VibratorToken v;
+ synchronized (mVibratorLock) {
+ v = mVibratorTokens.get(token);
+ if (v == null) {
+ v = new VibratorToken(deviceId, token, mNextVibratorTokenValue++);
+ try {
+ token.linkToDeath(v, 0);
+ } catch (RemoteException ex) {
+ // give up
+ throw new RuntimeException(ex);
+ }
+ mVibratorTokens.put(token, v);
+ }
+ }
+
+ synchronized (v) {
+ v.mVibrating = true;
+ nativeVibrate(mPtr, deviceId, pattern, repeat, v.mTokenValue);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void cancelVibrate(int deviceId, IBinder token) {
+ VibratorToken v;
+ synchronized (mVibratorLock) {
+ v = mVibratorTokens.get(token);
+ if (v == null || v.mDeviceId != deviceId) {
+ return; // nothing to cancel
+ }
+ }
+
+ cancelVibrateIfNeeded(v);
+ }
+
+ void onVibratorTokenDied(VibratorToken v) {
+ synchronized (mVibratorLock) {
+ mVibratorTokens.remove(v.mToken);
+ }
+
+ cancelVibrateIfNeeded(v);
+ }
+
+ private void cancelVibrateIfNeeded(VibratorToken v) {
+ synchronized (v) {
+ if (v.mVibrating) {
+ nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+ v.mVibrating = false;
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
@@ -741,6 +906,18 @@
}
// Native callback.
+ private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
+ synchronized (mInputDevicesLock) {
+ mInputDevices = inputDevices;
+
+ if (!mInputDevicesChangedPending) {
+ mInputDevicesChangedPending = true;
+ mHandler.sendEmptyMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED);
+ }
+ }
+ }
+
+ // Native callback.
private void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
mCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
}
@@ -906,6 +1083,20 @@
}
/**
+ * Private handler for the input manager.
+ */
+ private final class InputManagerHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_DELIVER_INPUT_DEVICES_CHANGED:
+ deliverInputDevicesChanged();
+ break;
+ }
+ }
+ }
+
+ /**
* Hosting interface for input filters to call back into the input manager.
*/
private final class InputFilterHost implements InputFilter.Host {
@@ -957,4 +1148,54 @@
return result;
}
}
+
+ private final class InputDevicesChangedListenerRecord implements DeathRecipient {
+ private final int mPid;
+ private final IInputDevicesChangedListener mListener;
+
+ public InputDevicesChangedListenerRecord(int pid, IInputDevicesChangedListener listener) {
+ mPid = pid;
+ mListener = listener;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Input devices changed listener for pid " + mPid + " died.");
+ }
+ onInputDevicesChangedListenerDied(mPid);
+ }
+
+ public void notifyInputDevicesChanged(int[] info) {
+ try {
+ mListener.onInputDevicesChanged(info);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that input devices changed, assuming it died.", ex);
+ binderDied();
+ }
+ }
+ }
+
+ private final class VibratorToken implements DeathRecipient {
+ public final int mDeviceId;
+ public final IBinder mToken;
+ public final int mTokenValue;
+
+ public boolean mVibrating;
+
+ public VibratorToken(int deviceId, IBinder token, int tokenValue) {
+ mDeviceId = deviceId;
+ mToken = token;
+ mTokenValue = tokenValue;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator token died.");
+ }
+ onVibratorTokenDied(this);
+ }
+ }
}
diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java
index fa62e497..1e17067 100644
--- a/services/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -50,6 +50,7 @@
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.telephony.TelephonyManager.SIM_STATE_READY;
import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static com.android.internal.util.ArrayUtils.appendInt;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.net.NetworkPolicyManagerService.XmlUtils.readBooleanAttribute;
@@ -1216,6 +1217,23 @@
}
@Override
+ public int[] getAppsWithPolicy(int policy) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ int[] appIds = new int[0];
+ synchronized (mRulesLock) {
+ for (int i = 0; i < mAppPolicy.size(); i++) {
+ final int appId = mAppPolicy.keyAt(i);
+ final int appPolicy = mAppPolicy.valueAt(i);
+ if (appPolicy == policy) {
+ appIds = appendInt(appIds, appId);
+ }
+ }
+ }
+ return appIds;
+ }
+
+ @Override
public void registerListener(INetworkPolicyListener listener) {
// TODO: create permission for observing network policy
mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
@@ -1373,6 +1391,22 @@
}
@Override
+ public boolean isNetworkMetered(NetworkState state) {
+ final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+
+ final NetworkPolicy policy;
+ synchronized (mRulesLock) {
+ policy = findPolicyForNetworkLocked(ident);
+ }
+
+ if (policy != null) {
+ return policy.metered;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mContext.enforceCallingOrSelfPermission(DUMP, TAG);
@@ -1796,11 +1830,6 @@
mHandler.getLooper().getQueue().addIdleHandler(handler);
}
- public static boolean isAirplaneModeOn(Context context) {
- return Settings.System.getInt(
- context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0;
- }
-
private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
final int size = source.size();
for (int i = 0; i < size; i++) {
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 4382a03..2a67e02 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -26,6 +26,7 @@
import static android.content.Intent.EXTRA_UID;
import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.isNetworkTypeMobile;
import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
@@ -33,7 +34,7 @@
import static android.net.NetworkStats.SET_FOREGROUND;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
import static android.net.TrafficStats.MB_IN_BYTES;
import static android.provider.Settings.Secure.NETSTATS_DEV_BUCKET_DURATION;
@@ -54,6 +55,8 @@
import static android.text.format.DateUtils.HOUR_IN_MILLIS;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.internal.util.ArrayUtils.appendElement;
+import static com.android.internal.util.ArrayUtils.contains;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
@@ -194,6 +197,8 @@
private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
/** Current default active iface. */
private String mActiveIface;
+ /** Set of any ifaces associated with mobile networks since boot. */
+ private String[] mMobileIfaces = new String[0];
private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
new DropBoxNonMonotonicObserver();
@@ -517,6 +522,11 @@
}
@Override
+ public String[] getMobileIfaces() {
+ return mMobileIfaces;
+ }
+
+ @Override
public void incrementOperationCount(int uid, int tag, int operationCount) {
if (Binder.getCallingUid() != uid) {
mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
@@ -735,6 +745,13 @@
}
ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
+
+ // remember any ifaces associated with mobile networks
+ if (isNetworkTypeMobile(state.networkInfo.getType())) {
+ if (!contains(mMobileIfaces, iface)) {
+ mMobileIfaces = appendElement(String.class, mMobileIfaces, iface);
+ }
+ }
}
}
}
@@ -861,7 +878,7 @@
NetworkStats.Entry uidTotal;
// collect mobile sample
- template = buildTemplateMobileAll(getActiveSubscriberId(mContext));
+ template = buildTemplateMobileWildcard();
devTotal = mDevRecorder.getTotalSinceBootLocked(template);
xtTotal = new NetworkStats.Entry();
uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
@@ -1022,12 +1039,6 @@
}
};
- private static String getActiveSubscriberId(Context context) {
- final TelephonyManager telephony = (TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE);
- return telephony.getSubscriberId();
- }
-
private boolean isBandwidthControlEnabled() {
try {
return mNetworkManager.isBandwidthControlEnabled();
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 1593707..b97d7fd 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -16,14 +16,14 @@
package com.android.server.pm;
+import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.ENFORCEMENT_DEFAULT;
-import static android.content.pm.PackageManager.ENFORCEMENT_YES;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
+import static com.android.internal.util.ArrayUtils.appendInt;
+import static com.android.internal.util.ArrayUtils.removeInt;
import static libcore.io.OsConstants.S_ISLNK;
import com.android.internal.app.IMediaContainerService;
@@ -141,6 +141,7 @@
import java.util.zip.ZipOutputStream;
import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
import libcore.io.Libcore;
/**
@@ -1453,22 +1454,6 @@
}
}
- static int[] appendInt(int[] cur, int val) {
- if (cur == null) {
- return new int[] { val };
- }
- final int N = cur.length;
- for (int i=0; i<N; i++) {
- if (cur[i] == val) {
- return cur;
- }
- }
- int[] ret = new int[N+1];
- System.arraycopy(cur, 0, ret, 0, N);
- ret[N] = val;
- return ret;
- }
-
static int[] appendInts(int[] cur, int[] add) {
if (add == null) return cur;
if (cur == null) return add;
@@ -1479,26 +1464,6 @@
return cur;
}
- static int[] removeInt(int[] cur, int val) {
- if (cur == null) {
- return null;
- }
- final int N = cur.length;
- for (int i=0; i<N; i++) {
- if (cur[i] == val) {
- int[] ret = new int[N-1];
- if (i > 0) {
- System.arraycopy(cur, 0, ret, 0, i);
- }
- if (i < (N-1)) {
- System.arraycopy(cur, i + 1, ret, i, N - i - 1);
- }
- return ret;
- }
- }
- return cur;
- }
-
static int[] removeInts(int[] cur, int[] rem) {
if (rem == null) return cur;
if (cur == null) return cur;
@@ -6616,6 +6581,7 @@
oldPackage = mPackages.get(pkgName);
if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
!= PackageManager.SIGNATURE_MATCH) {
+ Slog.w(TAG, "New package has a different signature: " + pkgName);
res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
return;
}
@@ -6981,9 +6947,8 @@
} catch (IOException e) {
Slog.e(TAG, "Couldn't create a new zip file for the public parts of a" +
" forward-locked app.");
+ destResourceFile.delete();
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
- } finally {
- //TODO clean up the extracted public files
}
retCode = mInstaller.setForwardLockPerm(getApkName(newPackage.mPath),
newPackage.applicationInfo.uid);
@@ -7025,38 +6990,34 @@
File publicZipFile) throws IOException {
final FileOutputStream fstr = new FileOutputStream(publicZipFile);
final ZipOutputStream publicZipOutStream = new ZipOutputStream(fstr);
- final ZipFile privateZip = new ZipFile(newPackage.mPath);
+ try {
+ final ZipFile privateZip = new ZipFile(newPackage.mPath);
+ try {
+ // Copy manifest, resources.arsc and res directory to public zip
- // Copy manifest, resources.arsc and res directory to public zip
-
- final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
- while (privateZipEntries.hasMoreElements()) {
- final ZipEntry zipEntry = privateZipEntries.nextElement();
- final String zipEntryName = zipEntry.getName();
- if ("AndroidManifest.xml".equals(zipEntryName)
- || "resources.arsc".equals(zipEntryName)
- || zipEntryName.startsWith("res/")) {
- try {
- copyZipEntry(zipEntry, privateZip, publicZipOutStream);
- } catch (IOException e) {
- try {
- publicZipOutStream.close();
- throw e;
- } finally {
- publicZipFile.delete();
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+ if ("AndroidManifest.xml".equals(zipEntryName)
+ || "resources.arsc".equals(zipEntryName)
+ || zipEntryName.startsWith("res/")) {
+ copyZipEntry(zipEntry, privateZip, publicZipOutStream);
}
}
+ } finally {
+ try { privateZip.close(); } catch (IOException e) { }
}
- }
- publicZipOutStream.finish();
- publicZipOutStream.flush();
- FileUtils.sync(fstr);
- publicZipOutStream.close();
- FileUtils.setPermissions(
- publicZipFile.getAbsolutePath(),
- FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP|FileUtils.S_IROTH,
- -1, -1);
+ publicZipOutStream.finish();
+ publicZipOutStream.flush();
+ FileUtils.sync(fstr);
+ publicZipOutStream.close();
+ FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
+ | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
+ } finally {
+ IoUtils.closeQuietly(publicZipOutStream);
+ }
}
private static void copyZipEntry(ZipEntry zipEntry,
@@ -7075,11 +7036,15 @@
}
outZipStream.putNextEntry(newEntry);
- InputStream data = inZipFile.getInputStream(zipEntry);
- while ((num = data.read(buffer)) > 0) {
- outZipStream.write(buffer, 0, num);
+ final InputStream data = inZipFile.getInputStream(zipEntry);
+ try {
+ while ((num = data.read(buffer)) > 0) {
+ outZipStream.write(buffer, 0, num);
+ }
+ outZipStream.flush();
+ } finally {
+ IoUtils.closeQuietly(data);
}
- outZipStream.flush();
}
private void deleteTempPackageFiles() {
@@ -9030,12 +8995,12 @@
}
@Override
- public void setPermissionEnforcement(String permission, int enforcement) {
+ public void setPermissionEnforced(String permission, boolean enforced) {
mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null);
if (READ_EXTERNAL_STORAGE.equals(permission)) {
synchronized (mPackages) {
- if (mSettings.mReadExternalStorageEnforcement != enforcement) {
- mSettings.mReadExternalStorageEnforcement = enforcement;
+ if (mSettings.mReadExternalStorageEnforced != enforced) {
+ mSettings.mReadExternalStorageEnforced = enforced;
mSettings.writeLPr();
// kill any non-foreground processes so we restart them and
@@ -9058,27 +9023,18 @@
}
@Override
- public int getPermissionEnforcement(String permission) {
+ public boolean isPermissionEnforced(String permission) {
mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null);
- if (READ_EXTERNAL_STORAGE.equals(permission)) {
- synchronized (mPackages) {
- return mSettings.mReadExternalStorageEnforcement;
- }
- } else {
- throw new IllegalArgumentException("No selective enforcement for " + permission);
+ synchronized (mPackages) {
+ return isPermissionEnforcedLocked(permission);
}
}
private boolean isPermissionEnforcedLocked(String permission) {
if (READ_EXTERNAL_STORAGE.equals(permission)) {
- switch (mSettings.mReadExternalStorageEnforcement) {
- case ENFORCEMENT_DEFAULT:
- return false;
- case ENFORCEMENT_YES:
- return true;
- }
+ return mSettings.mReadExternalStorageEnforced;
+ } else {
+ return true;
}
-
- return true;
}
}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index bb7f4fc..35b6bde 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -20,7 +20,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.ENFORCEMENT_DEFAULT;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
@@ -111,7 +111,7 @@
int mInternalSdkPlatform;
int mExternalSdkPlatform;
- int mReadExternalStorageEnforcement = ENFORCEMENT_DEFAULT;
+ boolean mReadExternalStorageEnforced = PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE;
/** Device identity for the purpose of package verification. */
private VerifierDeviceIdentity mVerifierDeviceIdentity;
@@ -1139,10 +1139,11 @@
serializer.endTag(null, "verifier");
}
- if (mReadExternalStorageEnforcement != ENFORCEMENT_DEFAULT) {
+ if (mReadExternalStorageEnforced
+ != PackageManager.DEFAULT_ENFORCE_READ_EXTERNAL_STORAGE) {
serializer.startTag(null, TAG_READ_EXTERNAL_STORAGE);
serializer.attribute(
- null, ATTR_ENFORCEMENT, Integer.toString(mReadExternalStorageEnforcement));
+ null, ATTR_ENFORCEMENT, mReadExternalStorageEnforced ? "1" : "0");
serializer.endTag(null, TAG_READ_EXTERNAL_STORAGE);
}
@@ -1547,10 +1548,7 @@
}
} else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
- try {
- mReadExternalStorageEnforcement = Integer.parseInt(enforcement);
- } catch (NumberFormatException e) {
- }
+ mReadExternalStorageEnforced = "1".equals(enforcement);
} else {
Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+ parser.getName());
@@ -2558,9 +2556,13 @@
if (p.perm != null) {
pw.print(" perm="); pw.println(p.perm);
}
+ if (READ_EXTERNAL_STORAGE.equals(p.name)) {
+ pw.print(" enforced=");
+ pw.println(mReadExternalStorageEnforced);
+ }
}
}
-
+
void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) {
boolean printedSomething = false;
for (SharedUserSetting su : mSharedUsers.values()) {
diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java
index e460f7fd..11af6ea 100644
--- a/services/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -18,6 +18,8 @@
import java.io.PrintWriter;
+import static com.android.server.wm.WindowStateAnimator.SurfaceTrace;
+
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
@@ -33,6 +35,7 @@
static final String TAG = "ScreenRotationAnimation";
static final boolean DEBUG_STATE = false;
static final boolean DEBUG_TRANSFORMS = false;
+ static final boolean TWO_PHASE_ANIMATION = false;
static final boolean USE_CUSTOM_BLACK_FRAME = false;
static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
@@ -44,7 +47,6 @@
BlackFrame mEnteringBlackFrame;
int mWidth, mHeight;
- int mSnapshotDeltaRotation;
int mOriginalRotation;
int mOriginalWidth, mOriginalHeight;
int mCurRotation;
@@ -138,10 +140,9 @@
if (mEnteringBlackFrame != null) {
mEnteringBlackFrame.printTo(prefix + " ", pw);
}
- pw.print(prefix); pw.print(" mSnapshotDeltaRotation="); pw.print(mSnapshotDeltaRotation);
- pw.print(" mCurRotation="); pw.println(mCurRotation);
- pw.print(prefix); pw.print("mOriginalRotation="); pw.print(mOriginalRotation);
- pw.print(" mOriginalWidth="); pw.print(mOriginalWidth);
+ pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
+ pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
+ pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
pw.print(" mAnimRunning="); pw.print(mAnimRunning);
@@ -206,17 +207,23 @@
">>> OPEN TRANSACTION ScreenRotationAnimation");
Surface.openTransaction();
}
-
+
try {
try {
- mSurface = new Surface(session, 0, "FreezeSurface",
- -1, mWidth, mHeight, PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
+ if (WindowManagerService.DEBUG_SURFACE_TRACE) {
+ mSurface = new SurfaceTrace(session, 0, "FreezeSurface", -1, mWidth, mHeight,
+ PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
+ } else {
+ mSurface = new Surface(session, 0, "FreezeSurface", -1, mWidth, mHeight,
+ PixelFormat.OPAQUE, Surface.FX_SURFACE_SCREENSHOT | Surface.HIDDEN);
+ }
if (!mSurface.isValid()) {
// Screenshot failed, punt.
mSurface = null;
return;
}
mSurface.setLayer(FREEZE_LAYER + 1);
+ mSurface.setAlpha(0);
mSurface.show();
} catch (Surface.OutOfResourcesException e) {
Slog.w(TAG, "Unable to allocate freeze surface", e);
@@ -306,8 +313,13 @@
public boolean setRotation(int rotation, SurfaceSession session,
long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
setRotation(rotation);
- return startAnimation(session, maxAnimationDuration, animationScale,
- finalWidth, finalHeight, false);
+ if (TWO_PHASE_ANIMATION) {
+ return startAnimation(session, maxAnimationDuration, animationScale,
+ finalWidth, finalHeight, false);
+ }
+
+ // Don't start animation yet.
+ return false;
}
/**
@@ -330,7 +342,8 @@
// Figure out how the screen has moved from the original rotation.
int delta = deltaRotation(mCurRotation, mOriginalRotation);
- if (mFinishExitAnimation == null && (!dismissing || delta != Surface.ROTATION_0)) {
+ if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null
+ && (!dismissing || delta != Surface.ROTATION_0)) {
if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations");
firstStart = true;
mStartExitAnimation = AnimationUtils.loadAnimation(mContext,
@@ -406,7 +419,7 @@
// means to allow supplying the last and next size. In this definition
// "%p" is the original (let's call it "previous") size, and "%" is the
// screen's current/new size.
- if (firstStart) {
+ if (TWO_PHASE_ANIMATION && firstStart) {
if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations");
mStartEnterAnimation.initialize(finalWidth, finalHeight,
halfWidth, halfHeight);
@@ -433,7 +446,7 @@
mFinishAnimReady = false;
mFinishAnimStartTime = -1;
- if (firstStart) {
+ if (TWO_PHASE_ANIMATION && firstStart) {
mStartExitAnimation.restrictDuration(maxAnimationDuration);
mStartExitAnimation.scaleCurrentDuration(animationScale);
mStartEnterAnimation.restrictDuration(maxAnimationDuration);
@@ -585,29 +598,37 @@
mEnteringBlackFrame.kill();
mEnteringBlackFrame = null;
}
- if (mStartExitAnimation != null) {
- mStartExitAnimation.cancel();
- mStartExitAnimation = null;
+ if (TWO_PHASE_ANIMATION) {
+ if (mStartExitAnimation != null) {
+ mStartExitAnimation.cancel();
+ mStartExitAnimation = null;
+ }
+ if (mStartEnterAnimation != null) {
+ mStartEnterAnimation.cancel();
+ mStartEnterAnimation = null;
+ }
+ if (mFinishExitAnimation != null) {
+ mFinishExitAnimation.cancel();
+ mFinishExitAnimation = null;
+ }
+ if (mFinishEnterAnimation != null) {
+ mFinishEnterAnimation.cancel();
+ mFinishEnterAnimation = null;
+ }
}
- if (mStartEnterAnimation != null) {
- mStartEnterAnimation.cancel();
- mStartEnterAnimation = null;
- }
- if (mStartFrameAnimation != null) {
- mStartFrameAnimation.cancel();
- mStartFrameAnimation = null;
- }
- if (mFinishExitAnimation != null) {
- mFinishExitAnimation.cancel();
- mFinishExitAnimation = null;
- }
- if (mFinishEnterAnimation != null) {
- mFinishEnterAnimation.cancel();
- mFinishEnterAnimation = null;
- }
- if (mFinishFrameAnimation != null) {
- mFinishFrameAnimation.cancel();
- mFinishFrameAnimation = null;
+ if (USE_CUSTOM_BLACK_FRAME) {
+ if (mStartFrameAnimation != null) {
+ mStartFrameAnimation.cancel();
+ mStartFrameAnimation = null;
+ }
+ if (mRotateFrameAnimation != null) {
+ mRotateFrameAnimation.cancel();
+ mRotateFrameAnimation = null;
+ }
+ if (mFinishFrameAnimation != null) {
+ mFinishFrameAnimation.cancel();
+ mFinishFrameAnimation = null;
+ }
}
if (mRotateExitAnimation != null) {
mRotateExitAnimation.cancel();
@@ -617,65 +638,71 @@
mRotateEnterAnimation.cancel();
mRotateEnterAnimation = null;
}
- if (mRotateFrameAnimation != null) {
- mRotateFrameAnimation.cancel();
- mRotateFrameAnimation = null;
- }
}
public boolean isAnimating() {
- return mStartEnterAnimation != null || mStartExitAnimation != null
- || mStartFrameAnimation != null
- || mFinishEnterAnimation != null || mFinishExitAnimation != null
- || mFinishFrameAnimation != null
- || mRotateEnterAnimation != null || mRotateExitAnimation != null
- || mRotateFrameAnimation != null;
+ return hasAnimations() || (TWO_PHASE_ANIMATION && mFinishAnimReady);
+ }
+
+ private boolean hasAnimations() {
+ return (TWO_PHASE_ANIMATION &&
+ (mStartEnterAnimation != null || mStartExitAnimation != null
+ || mFinishEnterAnimation != null || mFinishExitAnimation != null))
+ || (USE_CUSTOM_BLACK_FRAME &&
+ (mStartFrameAnimation != null || mRotateFrameAnimation != null
+ || mFinishFrameAnimation != null))
+ || mRotateEnterAnimation != null || mRotateExitAnimation != null;
}
private boolean stepAnimation(long now) {
-
if (mFinishAnimReady && mFinishAnimStartTime < 0) {
if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready");
mFinishAnimStartTime = now;
}
- mMoreStartExit = false;
- if (mStartExitAnimation != null) {
- mMoreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation);
- }
+ if (TWO_PHASE_ANIMATION) {
+ mMoreStartExit = false;
+ if (mStartExitAnimation != null) {
+ mMoreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation);
+ }
- mMoreStartEnter = false;
- if (mStartEnterAnimation != null) {
- mMoreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation);
+ mMoreStartEnter = false;
+ if (mStartEnterAnimation != null) {
+ mMoreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation);
+ }
}
-
- mMoreStartFrame = false;
- if (mStartFrameAnimation != null) {
- mMoreStartFrame = mStartFrameAnimation.getTransformation(now, mStartFrameTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start frame: " + mStartFrameTransformation);
+ if (USE_CUSTOM_BLACK_FRAME) {
+ mMoreStartFrame = false;
+ if (mStartFrameAnimation != null) {
+ mMoreStartFrame = mStartFrameAnimation.getTransformation(now, mStartFrameTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start frame: " + mStartFrameTransformation);
+ }
}
long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0;
if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow);
- mMoreFinishExit = false;
- if (mFinishExitAnimation != null) {
- mMoreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation);
- }
+ if (TWO_PHASE_ANIMATION) {
+ mMoreFinishExit = false;
+ if (mFinishExitAnimation != null) {
+ mMoreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation);
+ }
- mMoreFinishEnter = false;
- if (mFinishEnterAnimation != null) {
- mMoreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation);
+ mMoreFinishEnter = false;
+ if (mFinishEnterAnimation != null) {
+ mMoreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation);
+ }
}
-
- mMoreFinishFrame = false;
- if (mFinishFrameAnimation != null) {
- mMoreFinishFrame = mFinishFrameAnimation.getTransformation(finishNow, mFinishFrameTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish frame: " + mFinishFrameTransformation);
+ if (USE_CUSTOM_BLACK_FRAME) {
+ mMoreFinishFrame = false;
+ if (mFinishFrameAnimation != null) {
+ mMoreFinishFrame = mFinishFrameAnimation.getTransformation(finishNow, mFinishFrameTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish frame: " + mFinishFrameTransformation);
+ }
}
mMoreRotateExit = false;
@@ -690,24 +717,28 @@
if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation);
}
- mMoreRotateFrame = false;
- if (mRotateFrameAnimation != null) {
- mMoreRotateFrame = mRotateFrameAnimation.getTransformation(now, mRotateFrameTransformation);
- if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate frame: " + mRotateFrameTransformation);
+ if (USE_CUSTOM_BLACK_FRAME) {
+ mMoreRotateFrame = false;
+ if (mRotateFrameAnimation != null) {
+ mMoreRotateFrame = mRotateFrameAnimation.getTransformation(now, mRotateFrameTransformation);
+ if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate frame: " + mRotateFrameTransformation);
+ }
}
- if (!mMoreStartExit && !mMoreRotateExit && !mMoreFinishExit) {
- if (mStartExitAnimation != null) {
- if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing start exit anim!");
- mStartExitAnimation.cancel();
- mStartExitAnimation = null;
- mStartExitTransformation.clear();
- }
- if (mFinishExitAnimation != null) {
- if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing finish exit anim!");
- mFinishExitAnimation.cancel();
- mFinishExitAnimation = null;
- mFinishExitTransformation.clear();
+ if (!mMoreRotateExit && (!TWO_PHASE_ANIMATION || (!mMoreStartExit && !mMoreFinishExit))) {
+ if (TWO_PHASE_ANIMATION) {
+ if (mStartExitAnimation != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing start exit anim!");
+ mStartExitAnimation.cancel();
+ mStartExitAnimation = null;
+ mStartExitTransformation.clear();
+ }
+ if (mFinishExitAnimation != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing finish exit anim!");
+ mFinishExitAnimation.cancel();
+ mFinishExitAnimation = null;
+ mFinishExitTransformation.clear();
+ }
}
if (mRotateExitAnimation != null) {
if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing rotate exit anim!");
@@ -717,18 +748,20 @@
}
}
- if (!mMoreStartEnter && !mMoreRotateEnter && !mMoreFinishEnter) {
- if (mStartEnterAnimation != null) {
- if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing start enter anim!");
- mStartEnterAnimation.cancel();
- mStartEnterAnimation = null;
- mStartEnterTransformation.clear();
- }
- if (mFinishEnterAnimation != null) {
- if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing finish enter anim!");
- mFinishEnterAnimation.cancel();
- mFinishEnterAnimation = null;
- mFinishEnterTransformation.clear();
+ if (!mMoreRotateEnter && (!TWO_PHASE_ANIMATION || (!mMoreStartEnter && !mMoreFinishEnter))) {
+ if (TWO_PHASE_ANIMATION) {
+ if (mStartEnterAnimation != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing start enter anim!");
+ mStartEnterAnimation.cancel();
+ mStartEnterAnimation = null;
+ mStartEnterTransformation.clear();
+ }
+ if (mFinishEnterAnimation != null) {
+ if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing finish enter anim!");
+ mFinishEnterAnimation.cancel();
+ mFinishEnterAnimation = null;
+ mFinishEnterTransformation.clear();
+ }
}
if (mRotateEnterAnimation != null) {
if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing rotate enter anim!");
@@ -760,12 +793,14 @@
}
mExitTransformation.set(mRotateExitTransformation);
- mExitTransformation.compose(mStartExitTransformation);
- mExitTransformation.compose(mFinishExitTransformation);
-
mEnterTransformation.set(mRotateEnterTransformation);
- mEnterTransformation.compose(mStartEnterTransformation);
- mEnterTransformation.compose(mFinishEnterTransformation);
+ if (TWO_PHASE_ANIMATION) {
+ mExitTransformation.compose(mStartExitTransformation);
+ mExitTransformation.compose(mFinishExitTransformation);
+
+ mEnterTransformation.compose(mStartEnterTransformation);
+ mEnterTransformation.compose(mFinishEnterTransformation);
+ }
if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation);
if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation);
@@ -781,9 +816,11 @@
if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final frame: " + mFrameTransformation);
}
- final boolean more = mMoreStartEnter || mMoreStartExit || mMoreStartFrame
- || mMoreFinishEnter || mMoreFinishExit || mMoreFinishFrame
- || mMoreRotateEnter || mMoreRotateExit || mMoreRotateFrame
+ final boolean more = (TWO_PHASE_ANIMATION
+ && (mMoreStartEnter || mMoreStartExit || mMoreFinishEnter || mMoreFinishExit))
+ || (USE_CUSTOM_BLACK_FRAME
+ && (mMoreStartFrame || mMoreRotateFrame || mMoreFinishFrame))
+ || mMoreRotateEnter || mMoreRotateExit
|| !mFinishAnimReady;
mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
@@ -794,6 +831,10 @@
}
void updateSurfaces() {
+ if (!mStarted) {
+ return;
+ }
+
if (mSurface != null) {
if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
@@ -832,9 +873,9 @@
setSnapshotTransform(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
}
-
+
public boolean stepAnimationLocked(long now) {
- if (!isAnimating()) {
+ if (!hasAnimations()) {
if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running");
mFinishAnimReady = false;
return false;
@@ -842,23 +883,30 @@
if (!mAnimRunning) {
if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate");
- if (mStartEnterAnimation != null) {
- mStartEnterAnimation.setStartTime(now);
+ if (TWO_PHASE_ANIMATION) {
+ if (mStartEnterAnimation != null) {
+ mStartEnterAnimation.setStartTime(now);
+ }
+ if (mStartExitAnimation != null) {
+ mStartExitAnimation.setStartTime(now);
+ }
+ if (mFinishEnterAnimation != null) {
+ mFinishEnterAnimation.setStartTime(0);
+ }
+ if (mFinishExitAnimation != null) {
+ mFinishExitAnimation.setStartTime(0);
+ }
}
- if (mStartExitAnimation != null) {
- mStartExitAnimation.setStartTime(now);
- }
- if (mStartFrameAnimation != null) {
- mStartFrameAnimation.setStartTime(now);
- }
- if (mFinishEnterAnimation != null) {
- mFinishEnterAnimation.setStartTime(0);
- }
- if (mFinishExitAnimation != null) {
- mFinishExitAnimation.setStartTime(0);
- }
- if (mFinishFrameAnimation != null) {
- mFinishFrameAnimation.setStartTime(0);
+ if (USE_CUSTOM_BLACK_FRAME) {
+ if (mStartFrameAnimation != null) {
+ mStartFrameAnimation.setStartTime(now);
+ }
+ if (mFinishFrameAnimation != null) {
+ mFinishFrameAnimation.setStartTime(0);
+ }
+ if (mRotateFrameAnimation != null) {
+ mRotateFrameAnimation.setStartTime(now);
+ }
}
if (mRotateEnterAnimation != null) {
mRotateEnterAnimation.setStartTime(now);
@@ -866,9 +914,6 @@
if (mRotateExitAnimation != null) {
mRotateExitAnimation.setStartTime(now);
}
- if (mRotateFrameAnimation != null) {
- mRotateFrameAnimation.setStartTime(now);
- }
mAnimRunning = true;
}
diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java
index 0be6612..7611a0f 100644
--- a/services/java/com/android/server/wm/WindowAnimator.java
+++ b/services/java/com/android/server/wm/WindowAnimator.java
@@ -22,7 +22,7 @@
import com.android.internal.policy.impl.PhoneWindowManager;
import java.io.PrintWriter;
-import java.util.HashSet;
+import java.util.ArrayList;
/**
* Singleton class that carries out the animations and Surface operations in a separate task
@@ -35,8 +35,7 @@
final Context mContext;
final WindowManagerPolicy mPolicy;
- HashSet<WindowStateAnimator> mWinAnimators = new HashSet<WindowStateAnimator>();
- HashSet<WindowStateAnimator> mFinished = new HashSet<WindowStateAnimator>();
+ ArrayList<WindowStateAnimator> mWinAnimators = new ArrayList<WindowStateAnimator>();
boolean mAnimating;
boolean mTokenMayBeDrawn;
@@ -158,9 +157,7 @@
}
}
- if (mScreenRotationAnimation != null &&
- (mScreenRotationAnimation.isAnimating() ||
- mScreenRotationAnimation.mFinishAnimReady)) {
+ if (mScreenRotationAnimation != null && mScreenRotationAnimation.isAnimating()) {
if (mScreenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
mAnimating = true;
} else {
@@ -174,6 +171,9 @@
private void updateWindowsAndWallpaperLocked() {
++mTransactionSequence;
+ ArrayList<WindowStateAnimator> unForceHiding = null;
+ boolean wallpaperInUnForceHiding = false;
+
for (int i = mService.mWindows.size() - 1; i >= 0; i--) {
WindowState win = mService.mWindows.get(i);
WindowStateAnimator winAnimator = win.mWinAnimator;
@@ -269,13 +269,12 @@
if (changed) {
if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
&& win.isVisibleNow() /*w.isReadyForDisplay()*/) {
- // Assume we will need to animate. If
- // we don't (because the wallpaper will
- // stay with the lock screen), then we will
- // clean up later.
- Animation a = mPolicy.createForceHideEnterAnimation();
- if (a != null) {
- winAnimator.setAnimation(a);
+ if (unForceHiding == null) {
+ unForceHiding = new ArrayList<WindowStateAnimator>();
+ }
+ unForceHiding.add(winAnimator);
+ if ((win.mAttrs.flags&WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER) != 0) {
+ wallpaperInUnForceHiding = true;
}
}
if (mCurrentFocus == null || mCurrentFocus.mLayer < win.mLayer) {
@@ -358,6 +357,17 @@
}
}
} // end forall windows
+
+ // If we have windows that are being show due to them no longer
+ // being force-hidden, apply the appropriate animation to them.
+ if (unForceHiding != null) {
+ for (int i=unForceHiding.size()-1; i>=0; i--) {
+ Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding);
+ if (a != null) {
+ unForceHiding.get(i).setAnimation(a);
+ }
+ }
+ }
}
private void testTokenMayBeDrawnLocked() {
@@ -421,6 +431,10 @@
mPendingLayoutChanges = 0;
mCurrentTime = SystemClock.uptimeMillis();
mBulkUpdateParams = 0;
+ mAnimating = false;
+ if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+ Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
+ }
// Update animations of all applications, including those
// associated with exiting/removed apps
@@ -437,16 +451,9 @@
mScreenRotationAnimation.updateSurfaces();
}
- mFinished.clear();
- for (final WindowStateAnimator winAnimator : mWinAnimators) {
- if (winAnimator.mSurface == null) {
- mFinished.add(winAnimator);
- } else {
- winAnimator.prepareSurfaceLocked(true);
- }
- }
- for (final WindowStateAnimator winAnimator : mFinished) {
- mWinAnimators.remove(winAnimator);
+ final int N = mWinAnimators.size();
+ for (int i = 0; i < N; i++) {
+ mWinAnimators.get(i).prepareSurfaceLocked(true);
}
if (mDimParams != null) {
@@ -465,13 +472,26 @@
mService.mBlackFrame.clearMatrix();
}
}
+
+ if (mService.mWatermark != null) {
+ mService.mWatermark.drawIfNeeded();
+ }
} catch (RuntimeException e) {
Log.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
Surface.closeTransaction();
}
- mService.bulkSetParameters(mBulkUpdateParams);
+ mService.bulkSetParameters(mBulkUpdateParams, mPendingLayoutChanges);
+
+ if (mAnimating) {
+ mService.scheduleAnimationLocked();
+ }
+ if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+ Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+ + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+ + " mPendingLayoutChanges=" + Integer.toHexString(mPendingLayoutChanges));
+ }
}
WindowState mCurrentFocus;
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index be0f807..0fe6921 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -175,6 +175,8 @@
static final boolean DEBUG_SCREENSHOT = false;
static final boolean DEBUG_BOOT = false;
static final boolean DEBUG_LAYOUT_REPEATS = true;
+ static final boolean DEBUG_SURFACE_TRACE = false;
+ static final boolean DEBUG_WINDOW_TRACE = false;
static final boolean SHOW_SURFACE_ALLOC = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
@@ -425,7 +427,7 @@
IInputMethodManager mInputMethodManager;
- SurfaceSession mFxSession;
+ final SurfaceSession mFxSession;
Watermark mWatermark;
StrictModeFlash mStrictModeFlash;
@@ -592,6 +594,7 @@
static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1;
static final int SET_FORCE_HIDING_CHANGED = 1 << 2;
static final int CLEAR_ORIENTATION_CHANGE_COMPLETE = 1 << 3;
+ static final int SET_TURN_ON_SCREEN = 1 << 4;
boolean mWallpaperForceHidingChanged = false;
boolean mWallpaperMayChange = false;
@@ -615,7 +618,20 @@
public void run() {
synchronized(mWindowMap) {
mAnimationScheduled = false;
- performLayoutAndPlaceSurfacesLocked();
+ // Update animations of all applications, including those
+ // associated with exiting/removed apps
+ synchronized (mAnimator) {
+ final ArrayList<WindowStateAnimator> winAnimators = mAnimator.mWinAnimators;
+ winAnimators.clear();
+ final int N = mWindows.size();
+ for (int i = 0; i < N; i++) {
+ final WindowStateAnimator winAnimator = mWindows.get(i).mWinAnimator;
+ if (winAnimator.mSurface != null) {
+ winAnimators.add(winAnimator);
+ }
+ }
+ mAnimator.animate();
+ }
}
}
}
@@ -862,6 +878,11 @@
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
+ mFxSession = new SurfaceSession();
+
+ Surface.openTransaction();
+ createWatermark();
+ Surface.closeTransaction();
}
public InputManagerService getInputManagerService() {
@@ -4918,7 +4939,11 @@
// have been drawn.
boolean haveBootMsg = false;
boolean haveApp = false;
+ // if the wallpaper service is disabled on the device, we're never going to have
+ // wallpaper, don't bother waiting for it
boolean haveWallpaper = false;
+ boolean wallpaperEnabled = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableWallpaperService);
boolean haveKeyguard = true;
final int N = mWindows.size();
for (int i=0; i<N; i++) {
@@ -4954,7 +4979,8 @@
if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
+ " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
- + " haveWall=" + haveWallpaper + " haveKeyguard=" + haveKeyguard);
+ + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+ + " haveKeyguard=" + haveKeyguard);
}
// If we are turning on the screen to show the boot message,
@@ -4966,7 +4992,8 @@
// If we are turning on the screen after the boot is completed
// normally, don't do so until we have the application and
// wallpaper.
- if (mSystemBooted && ((!haveApp && !haveKeyguard) || !haveWallpaper)) {
+ if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
+ (wallpaperEnabled && !haveWallpaper))) {
return;
}
}
@@ -6475,6 +6502,9 @@
@Override
public void handleMessage(Message msg) {
+ if (DEBUG_WINDOW_TRACE) {
+ Slog.v(TAG, "handleMessage: entry what=" + msg.what);
+ }
switch (msg.what) {
case REPORT_FOCUS_CHANGE: {
WindowState lastFocus;
@@ -6906,6 +6936,14 @@
doRequest = true;
}
}
+ if ((msg.arg1 & LayoutFields.SET_TURN_ON_SCREEN) != 0) {
+ mTurnOnScreen = true;
+ }
+
+ mPendingLayoutChanges |= msg.arg2;
+ if (mPendingLayoutChanges != 0) {
+ doRequest = true;
+ }
if (doRequest) {
requestTraversalLocked();
@@ -6950,6 +6988,9 @@
break;
}
}
+ if (DEBUG_WINDOW_TRACE) {
+ Slog.v(TAG, "handleMessage: exit");
+ }
}
}
@@ -6957,6 +6998,7 @@
// IWindowManager API
// -------------------------------------------------------------
+ @Override
public IWindowSession openSession(IInputMethodClient client,
IInputContext inputContext) {
if (client == null) throw new IllegalArgumentException("null client");
@@ -6965,6 +7007,7 @@
return session;
}
+ @Override
public boolean inputMethodClientHasFocus(IInputMethodClient client) {
synchronized (mWindowMap) {
// The focus for the client is the window immediately below
@@ -7398,12 +7441,6 @@
} else {
mLayoutRepeatCount = 0;
}
-
- if (mAnimator.mAnimating) {
- // Do this even if requestTraversalLocked was called above so we get a frame drawn
- // at the proper time as well as the one drawn early.
- scheduleAnimationLocked();
- }
if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
@@ -7586,7 +7623,8 @@
if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
"Check opening app" + wtoken + ": allDrawn="
+ wtoken.allDrawn + " startingDisplayed="
- + wtoken.startingDisplayed);
+ + wtoken.startingDisplayed + " startingMoved="
+ + wtoken.startingMoved);
if (!wtoken.allDrawn && !wtoken.startingDisplayed
&& !wtoken.startingMoved) {
goodToGo = false;
@@ -7888,28 +7926,6 @@
if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper
+ " NEW: " + mWallpaperTarget
+ " LOWER: " + mLowerWallpaperTarget);
- if (mLowerWallpaperTarget == null) {
- // Whoops, we don't need a special wallpaper animation.
- // Clear them out.
- mAnimator.mForceHiding = false;
- for (int i=mWindows.size()-1; i>=0; i--) {
- WindowState w = mWindows.get(i);
- if (w.mHasSurface) {
- final WindowManager.LayoutParams attrs = w.mAttrs;
- if (mPolicy.doesForceHide(w, attrs) && w.isVisibleLw()) {
- if (DEBUG_FOCUS) Slog.i(TAG, "win=" + w + " force hides other windows");
- mAnimator.mForceHiding = true;
- } else if (mPolicy.canBeForceHidden(w, attrs)) {
- if (!w.mWinAnimator.mAnimating) {
- // We set the animation above so it
- // is not yet running.
- // TODO(cmautner): We lose the enter animation when this occurs.
- w.mWinAnimator.clearAnimation();
- }
- }
- }
- }
- }
return changes;
}
@@ -8042,6 +8058,9 @@
// "Something has changed! Let's make it correct now."
private final void performLayoutAndPlaceSurfacesLockedInner(
boolean recoveringMemory) {
+ if (DEBUG_WINDOW_TRACE) {
+ Slog.v(TAG, "performLayoutAndPlaceSurfacesLockedInner: entry");
+ }
if (mDisplay == null) {
Slog.i(TAG, "skipping performLayoutAndPlaceSurfacesLockedInner with no mDisplay");
return;
@@ -8074,23 +8093,12 @@
mInnerFields.mHoldScreen = null;
mInnerFields.mScreenBrightness = -1;
mInnerFields.mButtonBrightness = -1;
- boolean focusDisplayed = false;
- mAnimator.mAnimating = false;
- boolean createWatermark = false;
-
- if (mFxSession == null) {
- mFxSession = new SurfaceSession();
- createWatermark = true;
- }
if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
Surface.openTransaction();
- if (createWatermark) {
- createWatermark();
- }
if (mWatermark != null) {
mWatermark.positionSurface(dw, dh);
}
@@ -8161,6 +8169,7 @@
mInnerFields.mDimming = false;
mInnerFields.mSyswin = false;
+ boolean focusDisplayed = false;
final int N = mWindows.size();
for (i=N-1; i>=0; i--) {
WindowState w = mWindows.get(i);
@@ -8184,6 +8193,10 @@
updateWallpaperVisibilityLocked();
}
}
+ if (focusDisplayed) {
+ mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
+ }
+
if (!mInnerFields.mDimming && mAnimator.isDimming()) {
mAnimator.stopDimming();
}
@@ -8308,19 +8321,6 @@
}
}
- // Update animations of all applications, including those
- // associated with exiting/removed apps
- mAnimator.animate();
- mPendingLayoutChanges |= mAnimator.mPendingLayoutChanges;
- if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()", mPendingLayoutChanges);
-
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
- "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
-
- if (mWatermark != null) {
- mWatermark.drawIfNeeded();
- }
-
if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG,
"With display frozen, orientationChangeComplete="
+ mInnerFields.mOrientationChangeComplete);
@@ -8431,9 +8431,6 @@
mToBottomApps.clear();
}
- if (focusDisplayed) {
- mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
- }
if (wallpaperDestroyed) {
mLayoutNeeded |= adjustWallpaperWindowsLocked() != 0;
}
@@ -8489,9 +8486,14 @@
// Check to see if we are now in a state where the screen should
// be enabled, because the window obscured flags have changed.
enableScreenIfNeededLocked();
-// Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: mPendingLayoutChanges="
-// + Integer.toHexString(mPendingLayoutChanges) + " mLayoutNeeded=" + mLayoutNeeded
-// + " animating=" + mAnimator.mAnimating);
+
+ scheduleAnimationLocked();
+
+ if (DEBUG_WINDOW_TRACE) {
+ Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: mPendingLayoutChanges="
+ + Integer.toHexString(mPendingLayoutChanges) + " mLayoutNeeded=" + mLayoutNeeded
+ + " animating=" + mAnimator.mAnimating);
+ }
}
void checkDrawnWindowsLocked() {
@@ -8604,7 +8606,6 @@
wsa.mSurfaceShown = false;
wsa.mSurface = null;
ws.mHasSurface = false;
- mAnimator.mWinAnimators.remove(wsa);
mForceRemoves.add(ws);
i--;
N--;
@@ -8618,7 +8619,6 @@
wsa.mSurfaceShown = false;
wsa.mSurface = null;
ws.mHasSurface = false;
- mAnimator.mWinAnimators.remove(wsa);
leakedSurface = true;
}
}
@@ -8658,7 +8658,6 @@
winAnimator.mSurfaceShown = false;
winAnimator.mSurface = null;
winAnimator.mWin.mHasSurface = false;
- mAnimator.mWinAnimators.remove(winAnimator);
}
try {
@@ -8806,9 +8805,9 @@
mScreenFrozenLock.acquire();
mDisplayFrozen = true;
-
+
mInputMonitor.freezeInputDispatchingLw();
-
+
if (mNextAppTransition != WindowManagerPolicy.TRANSIT_UNSET) {
mNextAppTransition = WindowManagerPolicy.TRANSIT_UNSET;
mNextAppTransitionPackage = null;
@@ -8867,6 +8866,7 @@
mTransitionAnimationScale, mCurDisplayWidth, mCurDisplayHeight)) {
scheduleAnimationLocked();
} else {
+ mAnimator.mScreenRotationAnimation.kill();
mAnimator.mScreenRotationAnimation = null;
updateRotation = true;
}
@@ -9534,12 +9534,31 @@
}
}
- void bulkSetParameters(final int bulkUpdateParams) {
- mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams, 0));
+ void bulkSetParameters(final int bulkUpdateParams, int pendingLayoutChanges) {
+ mH.sendMessage(mH.obtainMessage(H.BULK_UPDATE_PARAMETERS, bulkUpdateParams,
+ pendingLayoutChanges));
+ }
+
+ /**
+ * Never call directly. Only call through getCallers(int) or getCaller(). Otherwise
+ * the depth will be off.
+ * @param depth What level stack to return.
+ * @return A String indicating who the caller of the method that calls this is.
+ */
+ static String getCaller(int depth) {
+ StackTraceElement caller = Thread.currentThread().getStackTrace()[5 + depth];
+ return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+ }
+
+ static String getCallers(final int depth) {
+ StringBuffer sb = new StringBuffer();
+ for (int i = 0; i < depth; i++) {
+ sb.append(getCaller(i)).append(" ");
+ }
+ return sb.toString();
}
static String getCaller() {
- StackTraceElement caller = Thread.currentThread().getStackTrace()[4];
- return caller.getClassName() + "." + caller.getMethodName() + ":" + caller.getLineNumber();
+ return getCallers(1);
}
}
diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java
index 941a5e1..90b63a6 100644
--- a/services/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/java/com/android/server/wm/WindowStateAnimator.java
@@ -6,15 +6,19 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static com.android.server.wm.WindowManagerService.LayoutFields.CLEAR_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_TURN_ON_SCREEN;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Surface;
+import android.view.SurfaceSession;
import android.view.WindowManager;
import android.view.WindowManagerPolicy;
import android.view.WindowManager.LayoutParams;
@@ -25,6 +29,7 @@
import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
+import java.util.ArrayList;
/**
* Keep track of animations and surface operations for a single WindowState.
@@ -39,6 +44,7 @@
static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC;
static final boolean localLOGV = WindowManagerService.localLOGV;
static final boolean DEBUG_ORIENTATION = WindowManagerService.DEBUG_ORIENTATION;
+ static final boolean DEBUG_SURFACE_TRACE = WindowManagerService.DEBUG_SURFACE_TRACE;
static final String TAG = "WindowStateAnimator";
@@ -79,9 +85,9 @@
*/
boolean mSurfaceDestroyDeferred;
- float mShownAlpha = 1;
- float mAlpha = 1;
- float mLastAlpha = 1;
+ float mShownAlpha = 0;
+ float mAlpha = 0;
+ float mLastAlpha = 0;
// Used to save animation distances between the time they are calculated and when they are
// used.
@@ -398,6 +404,116 @@
return true;
}
+ static class SurfaceTrace extends Surface {
+ private final static String SURFACE_TAG = "SurfaceTrace";
+ final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
+
+ private float mSurfaceTraceAlpha = 0;
+ private int mLayer;
+ private PointF mPosition = new PointF();
+ private Point mSize;
+ private boolean mShown = false;
+ private String mName = "Not named";
+
+ public SurfaceTrace(SurfaceSession s,
+ int pid, int display, int w, int h, int format, int flags) throws
+ OutOfResourcesException {
+ super(s, pid, display, w, h, format, flags);
+ mSize = new Point(w, h);
+ Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ public SurfaceTrace(SurfaceSession s,
+ int pid, String name, int display, int w, int h, int format, int flags)
+ throws OutOfResourcesException {
+ super(s, pid, name, display, w, h, format, flags);
+ mName = name;
+ mSize = new Point(w, h);
+ Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mSurfaceTraceAlpha = alpha;
+ Slog.v(SURFACE_TAG, "setAlpha: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ @Override
+ public void setLayer(int zorder) {
+ super.setLayer(zorder);
+ mLayer = zorder;
+ Slog.v(SURFACE_TAG, "setLayer: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+
+ sSurfaces.remove(this);
+ int i;
+ for (i = sSurfaces.size() - 1; i >= 0; i--) {
+ SurfaceTrace s = sSurfaces.get(i);
+ if (s.mLayer < zorder) {
+ break;
+ }
+ }
+ sSurfaces.add(i + 1, this);
+ }
+
+ @Override
+ public void setPosition(float x, float y) {
+ super.setPosition(x, y);
+ mPosition = new PointF(x, y);
+ Slog.v(SURFACE_TAG, "setPosition: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ @Override
+ public void setSize(int w, int h) {
+ super.setSize(w, h);
+ mSize = new Point(w, h);
+ Slog.v(SURFACE_TAG, "setSize: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ @Override
+ public void hide() {
+ super.hide();
+ mShown = false;
+ Slog.v(SURFACE_TAG, "hide: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+ @Override
+ public void show() {
+ super.show();
+ mShown = true;
+ Slog.v(SURFACE_TAG, "show: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ }
+
+ @Override
+ public void destroy() {
+ super.destroy();
+ Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by "
+ + WindowManagerService.getCallers(3));
+ sSurfaces.remove(this);
+ }
+
+ static void dumpAllSurfaces() {
+ final int N = sSurfaces.size();
+ for (int i = 0; i < N; i++) {
+ Slog.i(TAG, "SurfaceDump: " + sSurfaces.get(i));
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Surface " + mName + ": shown=" + mShown + " layer=" + mLayer
+ + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
+ + " " + mSize.x + "x" + mSize.y;
+ }
+ }
+
Surface createSurfaceLocked() {
if (mSurface == null) {
mReportDestroySurface = false;
@@ -440,7 +556,7 @@
mSurfaceShown = false;
mSurfaceLayer = 0;
- mSurfaceAlpha = 1;
+ mSurfaceAlpha = 0;
mSurfaceX = 0;
mSurfaceY = 0;
mSurfaceW = w;
@@ -452,12 +568,18 @@
if (!PixelFormat.formatHasAlpha(attrs.format)) {
flags |= Surface.OPAQUE;
}
- mSurface = new Surface(
+ if (DEBUG_SURFACE_TRACE) {
+ mSurface = new SurfaceTrace(
+ mSession.mSurfaceSession, mSession.mPid,
+ attrs.getTitle().toString(),
+ 0, w, h, format, flags);
+ } else {
+ mSurface = new Surface(
mSession.mSurfaceSession, mSession.mPid,
attrs.getTitle().toString(),
0, w, h, format, flags);
+ }
mWin.mHasSurface = true;
- mAnimator.mWinAnimators.add(this);
if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
" CREATE SURFACE "
+ mSurface + " IN SESSION "
@@ -468,14 +590,12 @@
+ " / " + this);
} catch (Surface.OutOfResourcesException e) {
mWin.mHasSurface = false;
- mAnimator.mWinAnimators.remove(this);
Slog.w(TAG, "OutOfResourcesException creating surface");
mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
mDrawState = NO_SURFACE;
return null;
} catch (Exception e) {
mWin.mHasSurface = false;
- mAnimator.mWinAnimators.remove(this);
Slog.e(TAG, "Exception creating surface", e);
mDrawState = NO_SURFACE;
return null;
@@ -500,6 +620,7 @@
mSurface.setPosition(mSurfaceX, mSurfaceY);
mSurfaceLayer = mAnimLayer;
mSurface.setLayer(mAnimLayer);
+ mSurface.setAlpha(0);
mSurfaceShown = false;
mSurface.hide();
if ((mWin.mAttrs.flags&WindowManager.LayoutParams.FLAG_DITHER) != 0) {
@@ -593,7 +714,6 @@
mSurfaceShown = false;
mSurface = null;
mWin.mHasSurface =false;
- mAnimator.mWinAnimators.remove(this);
}
}
@@ -1078,7 +1198,7 @@
if (DEBUG_VISIBILITY) Slog.v(TAG,
"Show surface turning screen on: " + mWin);
mWin.mTurnOnScreen = false;
- mService.mTurnOnScreen = true;
+ mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
}
}
return true;
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index 85d6e11..3795074 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -59,6 +59,7 @@
static struct {
jmethodID notifyConfigurationChanged;
+ jmethodID notifyInputDevicesChanged;
jmethodID notifyLidSwitchChanged;
jmethodID notifyInputChannelBroken;
jmethodID notifyANR;
@@ -82,6 +83,10 @@
static struct {
jclass clazz;
+} gInputDeviceClassInfo;
+
+static struct {
+ jclass clazz;
} gKeyEventClassInfo;
static struct {
@@ -179,6 +184,7 @@
virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+ virtual void notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -486,6 +492,36 @@
}
}
+void NativeInputManager::notifyInputDevicesChanged(const Vector<InputDeviceInfo>& inputDevices) {
+ JNIEnv* env = jniEnv();
+
+ size_t count = inputDevices.size();
+ jobjectArray inputDevicesObjArray = env->NewObjectArray(
+ count, gInputDeviceClassInfo.clazz, NULL);
+ if (inputDevicesObjArray) {
+ bool error = false;
+ for (size_t i = 0; i < count; i++) {
+ jobject inputDeviceObj = android_view_InputDevice_create(env, inputDevices.itemAt(i));
+ if (!inputDeviceObj) {
+ error = true;
+ break;
+ }
+
+ env->SetObjectArrayElement(inputDevicesObjArray, i, inputDeviceObj);
+ env->DeleteLocalRef(inputDeviceObj);
+ }
+
+ if (!error) {
+ env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyInputDevicesChanged,
+ inputDevicesObjArray);
+ }
+
+ env->DeleteLocalRef(inputDevicesObjArray);
+ }
+
+ checkAndClearExceptionFromCallback(env, "notifyInputDevicesChanged");
+}
+
void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
int32_t switchValue, uint32_t policyFlags) {
#if DEBUG_INPUT_DISPATCHER_POLICY
@@ -1147,36 +1183,6 @@
im->setSystemUiVisibility(visibility);
}
-static jobject nativeGetInputDevice(JNIEnv* env,
- jclass clazz, jint ptr, jint deviceId) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
- InputDeviceInfo deviceInfo;
- status_t status = im->getInputManager()->getReader()->getInputDeviceInfo(
- deviceId, & deviceInfo);
- if (status) {
- return NULL;
- }
-
- return android_view_InputDevice_create(env, deviceInfo);
-}
-
-static jintArray nativeGetInputDeviceIds(JNIEnv* env,
- jclass clazz, jint ptr) {
- NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
- Vector<int> deviceIds;
- im->getInputManager()->getReader()->getInputDeviceIds(deviceIds);
-
- jintArray deviceIdsObj = env->NewIntArray(deviceIds.size());
- if (! deviceIdsObj) {
- return NULL;
- }
-
- env->SetIntArrayRegion(deviceIdsObj, 0, deviceIds.size(), deviceIds.array());
- return deviceIdsObj;
-}
-
static void nativeGetInputConfiguration(JNIEnv* env,
jclass clazz, jint ptr, jobject configObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1220,6 +1226,38 @@
im->setShowTouches(enabled);
}
+static void nativeVibrate(JNIEnv* env,
+ jclass clazz, jint ptr, jint deviceId, jlongArray patternObj,
+ jint repeat, jint token) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ size_t patternSize = env->GetArrayLength(patternObj);
+ if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
+ ALOGI("Skipped requested vibration because the pattern size is %d "
+ "which is more than the maximum supported size of %d.",
+ patternSize, MAX_VIBRATE_PATTERN_SIZE);
+ return; // limit to reasonable size
+ }
+
+ jlong* patternMillis = static_cast<jlong*>(env->GetPrimitiveArrayCritical(
+ patternObj, NULL));
+ nsecs_t pattern[patternSize];
+ for (size_t i = 0; i < patternSize; i++) {
+ pattern[i] = max(jlong(0), min(patternMillis[i],
+ MAX_VIBRATE_PATTERN_DELAY_NSECS / 1000000LL)) * 1000000LL;
+ }
+ env->ReleasePrimitiveArrayCritical(patternObj, patternMillis, JNI_ABORT);
+
+ im->getInputManager()->getReader()->vibrate(deviceId, pattern, patternSize, repeat, token);
+}
+
+static void nativeCancelVibrate(JNIEnv* env,
+ jclass clazz, jint ptr, jint deviceId, jint token) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getReader()->cancelVibrate(deviceId, token);
+}
+
static jstring nativeDump(JNIEnv* env, jclass clazz, jint ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1273,10 +1311,6 @@
(void*) nativeSetInputDispatchMode },
{ "nativeSetSystemUiVisibility", "(II)V",
(void*) nativeSetSystemUiVisibility },
- { "nativeGetInputDevice", "(II)Landroid/view/InputDevice;",
- (void*) nativeGetInputDevice },
- { "nativeGetInputDeviceIds", "(I)[I",
- (void*) nativeGetInputDeviceIds },
{ "nativeGetInputConfiguration", "(ILandroid/content/res/Configuration;)V",
(void*) nativeGetInputConfiguration },
{ "nativeTransferTouchFocus", "(ILandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
@@ -1285,6 +1319,10 @@
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(IZ)V",
(void*) nativeSetShowTouches },
+ { "nativeVibrate", "(II[JII)V",
+ (void*) nativeVibrate },
+ { "nativeCancelVibrate", "(III)V",
+ (void*) nativeCancelVibrate },
{ "nativeDump", "(I)Ljava/lang/String;",
(void*) nativeDump },
{ "nativeMonitor", "(I)V",
@@ -1316,6 +1354,9 @@
GET_METHOD_ID(gServiceClassInfo.notifyConfigurationChanged, clazz,
"notifyConfigurationChanged", "(J)V");
+ GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
+ "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");
+
GET_METHOD_ID(gServiceClassInfo.notifyLidSwitchChanged, clazz,
"notifyLidSwitchChanged", "(JZ)V");
@@ -1377,6 +1418,11 @@
GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz,
"getPointerIcon", "()Landroid/view/PointerIcon;");
+ // InputDevice
+
+ FIND_CLASS(gInputDeviceClassInfo.clazz, "android/view/InputDevice");
+ gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
+
// KeyEvent
FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent");
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 486a924..a124c7f 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -35,6 +35,7 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
@@ -208,6 +209,27 @@
protected static final int EVENT_RIL_CONNECTED = BASE + 5;
protected static final int EVENT_DISCONNECT_ALL = BASE + 6;
+ private static final int CMD_TO_STRING_COUNT = EVENT_DISCONNECT_ALL + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+ static {
+ sCmdToString[EVENT_CONNECT - BASE] = "EVENT_CONNECT";
+ sCmdToString[EVENT_SETUP_DATA_CONNECTION_DONE - BASE] =
+ "EVENT_SETUP_DATA_CONNECTION_DONE";
+ sCmdToString[EVENT_GET_LAST_FAIL_DONE - BASE] = "EVENT_GET_LAST_FAIL_DONE";
+ sCmdToString[EVENT_DEACTIVATE_DONE - BASE] = "EVENT_DEACTIVATE_DONE";
+ sCmdToString[EVENT_DISCONNECT - BASE] = "EVENT_DISCONNECT";
+ sCmdToString[EVENT_RIL_CONNECTED - BASE] = "EVENT_RIL_CONNECTED";
+ sCmdToString[EVENT_DISCONNECT_ALL - BASE] = "EVENT_DISCONNECT_ALL";
+ }
+ protected static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return null;
+ }
+ }
+
//***** Tag IDs for EventLog
protected static final int EVENT_LOG_BAD_DNS_ADDRESS = 50100;
@@ -242,6 +264,7 @@
protected DataConnection(PhoneBase phone, String name, int id, RetryManager rm,
DataConnectionTracker dct) {
super(name);
+ setProcessedMessagesSize(100);
if (DBG) log("DataConnection constructor E");
this.phone = phone;
this.mDataConnectionTracker = dct;
@@ -1188,26 +1211,65 @@
new DisconnectParams(reason, onCompletedMsg)));
}
+ /**
+ * @return the string for msg.what as our info.
+ */
+ @Override
+ protected String getMessageInfo(Message msg) {
+ String info = null;
+ info = cmdToString(msg.what);
+ if (info == null) {
+ info = DataConnectionAc.cmdToString(msg.what);
+ }
+ return info;
+ }
+
+ /**
+ * Convert a System.currentTimeMillis() value to a time of day value.
+ *
+ * @param millis since the epoch (1/1/1970)
+ * @return String representation of the time.
+ */
+ private String timeMillisToTimeOfDay(long millis) {
+ Calendar c = Calendar.getInstance();
+ if (millis >= 0) {
+ c.setTimeInMillis(millis);
+ return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
+ } else {
+ return Long.toString(millis);
+ }
+ }
+ /**
+ * Dump the current state.
+ *
+ * @param fd
+ * @param pw
+ * @param args
+ */
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("DataConnection name=" + getName() + ":");
+ pw.print("DataConnection ");
+ super.dump(fd, pw, args);
pw.println(" mApnList=" + mApnList);
+ pw.flush();
pw.println(" mDataConnectionTracker=" + mDataConnectionTracker);
pw.println(" mApn=" + mApn);
pw.println(" mTag=" + mTag);
+ pw.flush();
pw.println(" phone=" + phone);
pw.println(" mRilVersion=" + mRilVersion);
pw.println(" cid=" + cid);
+ pw.flush();
pw.println(" mLinkProperties=" + mLinkProperties);
+ pw.flush();
pw.println(" mCapabilities=" + mCapabilities);
- pw.println(" createTime=" + createTime);
- pw.println(" lastFailTime=" + lastFailTime);
+ pw.println(" createTime=" + timeMillisToTimeOfDay(createTime));
+ pw.println(" lastFailTime=" + timeMillisToTimeOfDay(lastFailTime));
pw.println(" lastFailCause=" + lastFailCause);
+ pw.flush();
pw.println(" mRetryOverride=" + mRetryOverride);
pw.println(" mRefCount=" + mRefCount);
pw.println(" userData=" + userData);
- pw.println(" total messages=" + getProcessedMessagesCount());
- for (int i=0; i < getProcessedMessagesSize(); i++) {
- pw.printf(" msg[%d]=%s\n", i, getProcessedMessageInfo(i));
- }
+ pw.flush();
}
}
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
index a9f2cd1..4744ff0 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -82,6 +82,51 @@
public static final int REQ_GET_RECONNECT_INTENT = BASE + 26;
public static final int RSP_GET_RECONNECT_INTENT = BASE + 27;
+ private static final int CMD_TO_STRING_COUNT = RSP_GET_RECONNECT_INTENT + 1;
+ private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+ static {
+ sCmdToString[REQ_IS_INACTIVE - BASE] = "REQ_IS_INACTIVE";
+ sCmdToString[RSP_IS_INACTIVE - BASE] = "RSP_IS_INACTIVE";
+ sCmdToString[REQ_GET_CID - BASE] = "REQ_GET_CID";
+ sCmdToString[RSP_GET_CID - BASE] = "RSP_GET_CID";
+ sCmdToString[REQ_GET_APNSETTING - BASE] = "REQ_GET_APNSETTING";
+ sCmdToString[RSP_GET_APNSETTING - BASE] = "RSP_GET_APNSETTING";
+ sCmdToString[REQ_GET_LINK_PROPERTIES - BASE] = "REQ_GET_LINK_PROPERTIES";
+ sCmdToString[RSP_GET_LINK_PROPERTIES - BASE] = "RSP_GET_LINK_PROPERTIES";
+ sCmdToString[REQ_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
+ "REQ_SET_LINK_PROPERTIES_HTTP_PROXY";
+ sCmdToString[RSP_SET_LINK_PROPERTIES_HTTP_PROXY - BASE] =
+ "RSP_SET_LINK_PROPERTIES_HTTP_PROXY";
+ sCmdToString[REQ_GET_LINK_CAPABILITIES - BASE] = "REQ_GET_LINK_CAPABILITIES";
+ sCmdToString[RSP_GET_LINK_CAPABILITIES - BASE] = "RSP_GET_LINK_CAPABILITIES";
+ sCmdToString[REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE - BASE] =
+ "REQ_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE";
+ sCmdToString[RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE - BASE] =
+ "RSP_UPDATE_LINK_PROPERTIES_DATA_CALL_STATE";
+ sCmdToString[REQ_RESET - BASE] = "REQ_RESET";
+ sCmdToString[RSP_RESET - BASE] = "RSP_RESET";
+ sCmdToString[REQ_GET_REFCOUNT - BASE] = "REQ_GET_REFCOUNT";
+ sCmdToString[RSP_GET_REFCOUNT - BASE] = "RSP_GET_REFCOUNT";
+ sCmdToString[REQ_ADD_APNCONTEXT - BASE] = "REQ_ADD_APNCONTEXT";
+ sCmdToString[RSP_ADD_APNCONTEXT - BASE] = "RSP_ADD_APNCONTEXT";
+ sCmdToString[REQ_REMOVE_APNCONTEXT - BASE] = "REQ_REMOVE_APNCONTEXT";
+ sCmdToString[RSP_REMOVE_APNCONTEXT - BASE] = "RSP_REMOVE_APNCONTEXT";
+ sCmdToString[REQ_GET_APNCONTEXT_LIST - BASE] = "REQ_GET_APNCONTEXT_LIST";
+ sCmdToString[RSP_GET_APNCONTEXT_LIST - BASE] = "RSP_GET_APNCONTEXT_LIST";
+ sCmdToString[REQ_SET_RECONNECT_INTENT - BASE] = "REQ_SET_RECONNECT_INTENT";
+ sCmdToString[RSP_SET_RECONNECT_INTENT - BASE] = "RSP_SET_RECONNECT_INTENT";
+ sCmdToString[REQ_GET_RECONNECT_INTENT - BASE] = "REQ_GET_RECONNECT_INTENT";
+ sCmdToString[RSP_GET_RECONNECT_INTENT - BASE] = "RSP_GET_RECONNECT_INTENT";
+ }
+ protected static String cmdToString(int cmd) {
+ cmd -= BASE;
+ if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+ return sCmdToString[cmd];
+ } else {
+ return AsyncChannel.cmdToString(cmd + BASE);
+ }
+ }
+
/**
* enum used to notify action taken or necessary to be
* taken after the link property is changed.
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 1b4cb15..2ac9365 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -250,7 +250,7 @@
// Initialize device storage and outgoing SMS usage monitors for SMSDispatchers.
mSmsStorageMonitor = new SmsStorageMonitor(this);
- mSmsUsageMonitor = new SmsUsageMonitor(context.getContentResolver());
+ mSmsUsageMonitor = new SmsUsageMonitor(context);
}
public void dispose() {
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index d6f96ff..28b729d 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -17,34 +17,40 @@
package com.android.internal.telephony;
import android.app.Activity;
-import android.app.PendingIntent;
import android.app.AlertDialog;
+import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.content.DialogInterface;
-import android.content.IntentFilter;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.net.Uri;
import android.os.AsyncResult;
+import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemProperties;
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
-import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
import android.telephony.SmsCbMessage;
import android.telephony.SmsMessage;
-import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.Spanned;
import android.util.Log;
import android.view.WindowManager;
+import com.android.internal.R;
import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
import com.android.internal.util.HexDump;
@@ -54,22 +60,17 @@
import java.util.HashMap;
import java.util.Random;
-import com.android.internal.R;
-
+import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
import static android.telephony.SmsManager.RESULT_ERROR_GENERIC_FAILURE;
+import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
import static android.telephony.SmsManager.RESULT_ERROR_NO_SERVICE;
import static android.telephony.SmsManager.RESULT_ERROR_NULL_PDU;
import static android.telephony.SmsManager.RESULT_ERROR_RADIO_OFF;
-import static android.telephony.SmsManager.RESULT_ERROR_LIMIT_EXCEEDED;
-import static android.telephony.SmsManager.RESULT_ERROR_FDN_CHECK_FAILURE;
public abstract class SMSDispatcher extends Handler {
static final String TAG = "SMS"; // accessed from inner class
private static final String SEND_NEXT_MSG_EXTRA = "SendNextMsg";
- /** Default timeout for SMS sent query */
- private static final int DEFAULT_SMS_TIMEOUT = 6000;
-
/** Permission required to receive SMS and SMS-CB messages. */
public static final String RECEIVE_SMS_PERMISSION = "android.permission.RECEIVE_SMS";
@@ -102,23 +103,27 @@
/** Retry sending a previously failed SMS message */
private static final int EVENT_SEND_RETRY = 3;
- /** SMS confirm required */
- private static final int EVENT_POST_ALERT = 4;
+ /** Confirmation required for sending a large number of messages. */
+ private static final int EVENT_SEND_LIMIT_REACHED_CONFIRMATION = 4;
/** Send the user confirmed SMS */
static final int EVENT_SEND_CONFIRMED_SMS = 5; // accessed from inner class
- /** Alert is timeout */
- private static final int EVENT_ALERT_TIMEOUT = 6;
-
- /** Stop the sending */
+ /** Don't send SMS (user did not confirm). */
static final int EVENT_STOP_SENDING = 7; // accessed from inner class
+ /** Confirmation required for third-party apps sending to an SMS short code. */
+ private static final int EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE = 8;
+
+ /** Confirmation required for third-party apps sending to an SMS short code. */
+ private static final int EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE = 9;
+
protected final Phone mPhone;
protected final Context mContext;
protected final ContentResolver mResolver;
protected final CommandsInterface mCm;
protected final SmsStorageMonitor mStorageMonitor;
+ protected final TelephonyManager mTelephonyManager;
protected final WapPushOverSms mWapPush;
@@ -144,7 +149,8 @@
/** Outgoing message counter. Shared by all dispatchers. */
private final SmsUsageMonitor mUsageMonitor;
- private final ArrayList<SmsTracker> mSTrackers = new ArrayList<SmsTracker>(MO_MSG_QUEUE_LIMIT);
+ /** Number of outgoing SmsTrackers waiting for user confirmation. */
+ private int mPendingTrackerCount;
/** Wake lock to ensure device stays awake while dispatching the SMS intent. */
private PowerManager.WakeLock mWakeLock;
@@ -182,6 +188,7 @@
mCm = phone.mCM;
mStorageMonitor = storageMonitor;
mUsageMonitor = usageMonitor;
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
createWakelock();
@@ -279,51 +286,44 @@
sendSms((SmsTracker) msg.obj);
break;
- case EVENT_POST_ALERT:
+ case EVENT_SEND_LIMIT_REACHED_CONFIRMATION:
handleReachSentLimit((SmsTracker)(msg.obj));
break;
- case EVENT_ALERT_TIMEOUT:
- ((AlertDialog)(msg.obj)).dismiss();
- msg.obj = null;
- if (mSTrackers.isEmpty() == false) {
- try {
- SmsTracker sTracker = mSTrackers.remove(0);
- sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
- } catch (CanceledException ex) {
- Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
- }
- }
- if (false) {
- Log.d(TAG, "EVENT_ALERT_TIMEOUT, message stop sending");
- }
+ case EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE:
+ handleConfirmShortCode(false, (SmsTracker)(msg.obj));
+ break;
+
+ case EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE:
+ handleConfirmShortCode(true, (SmsTracker)(msg.obj));
break;
case EVENT_SEND_CONFIRMED_SMS:
- if (mSTrackers.isEmpty() == false) {
- SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
- if (sTracker.isMultipart()) {
- sendMultipartSms(sTracker);
- } else {
- sendSms(sTracker);
- }
- removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
+ {
+ SmsTracker tracker = (SmsTracker) msg.obj;
+ if (tracker.isMultipart()) {
+ sendMultipartSms(tracker);
+ } else {
+ sendSms(tracker);
}
+ mPendingTrackerCount--;
break;
+ }
case EVENT_STOP_SENDING:
- if (mSTrackers.isEmpty() == false) {
- // Remove the latest one.
+ {
+ SmsTracker tracker = (SmsTracker) msg.obj;
+ if (tracker.mSentIntent != null) {
try {
- SmsTracker sTracker = mSTrackers.remove(mSTrackers.size() - 1);
- sTracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
+ tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
- Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
+ Log.e(TAG, "failed to send RESULT_ERROR_LIMIT_EXCEEDED");
}
- removeMessages(EVENT_ALERT_TIMEOUT, msg.obj);
}
+ mPendingTrackerCount--;
break;
}
+ }
}
private void createWakelock() {
@@ -397,7 +397,7 @@
int ss = mPhone.getServiceState().getState();
if (ss != ServiceState.STATE_IN_SERVICE) {
- handleNotInService(ss, tracker);
+ handleNotInService(ss, tracker.mSentIntent);
} else if ((((CommandException)(ar.exception)).getCommandError()
== CommandException.Error.SMS_FAIL_RETRY) &&
tracker.mRetryCount < MAX_SEND_RETRIES) {
@@ -446,15 +446,15 @@
* OUT_OF_SERVICE
* EMERGENCY_ONLY
* POWER_OFF
- * @param tracker An SmsTracker for the current message.
+ * @param sentIntent the PendingIntent to send the error to
*/
- protected static void handleNotInService(int ss, SmsTracker tracker) {
- if (tracker.mSentIntent != null) {
+ protected static void handleNotInService(int ss, PendingIntent sentIntent) {
+ if (sentIntent != null) {
try {
if (ss == ServiceState.STATE_POWER_OFF) {
- tracker.mSentIntent.send(RESULT_ERROR_RADIO_OFF);
+ sentIntent.send(RESULT_ERROR_RADIO_OFF);
} else {
- tracker.mSentIntent.send(RESULT_ERROR_NO_SERVICE);
+ sentIntent.send(RESULT_ERROR_NO_SERVICE);
}
} catch (CanceledException ex) {}
}
@@ -865,9 +865,10 @@
* @param deliveryIntent if not NULL this <code>Intent</code> is
* broadcast when the message is delivered to the recipient. The
* raw pdu of the status report is in the extended data ("pdu").
+ * @param destAddr the destination phone number (for short code confirmation)
*/
protected void sendRawPdu(byte[] smsc, byte[] pdu, PendingIntent sentIntent,
- PendingIntent deliveryIntent) {
+ PendingIntent deliveryIntent, String destAddr) {
if (mSmsSendDisabled) {
if (sentIntent != null) {
try {
@@ -891,61 +892,190 @@
map.put("smsc", smsc);
map.put("pdu", pdu);
- SmsTracker tracker = new SmsTracker(map, sentIntent,
- deliveryIntent);
- int ss = mPhone.getServiceState().getState();
+ // Get calling app package name via UID from Binder call
+ PackageManager pm = mContext.getPackageManager();
+ String[] packageNames = pm.getPackagesForUid(Binder.getCallingUid());
- if (ss != ServiceState.STATE_IN_SERVICE) {
- handleNotInService(ss, tracker);
- } else {
- String appName = getAppNameByIntent(sentIntent);
- if (mUsageMonitor.check(appName, SINGLE_PART_SMS)) {
- sendSms(tracker);
+ if (packageNames == null || packageNames.length == 0) {
+ // Refuse to send SMS if we can't get the calling package name.
+ Log.e(TAG, "Can't get calling app package name: refusing to send SMS");
+ if (sentIntent != null) {
+ try {
+ sentIntent.send(RESULT_ERROR_GENERIC_FAILURE);
+ } catch (CanceledException ex) {
+ Log.e(TAG, "failed to send error result");
+ }
+ }
+ return;
+ }
+
+ String appPackage = packageNames[0];
+
+ // Strip non-digits from destination phone number before checking for short codes
+ // and before displaying the number to the user if confirmation is required.
+ SmsTracker tracker = new SmsTracker(map, sentIntent, deliveryIntent, appPackage,
+ PhoneNumberUtils.extractNetworkPortion(destAddr));
+
+ // checkDestination() returns true if the destination is not a premium short code or the
+ // sending app is approved to send to short codes. Otherwise, a message is sent to our
+ // handler with the SmsTracker to request user confirmation before sending.
+ if (checkDestination(tracker)) {
+ // check for excessive outgoing SMS usage by this app
+ if (!mUsageMonitor.check(appPackage, SINGLE_PART_SMS)) {
+ sendMessage(obtainMessage(EVENT_SEND_LIMIT_REACHED_CONFIRMATION, tracker));
+ return;
+ }
+
+ int ss = mPhone.getServiceState().getState();
+
+ if (ss != ServiceState.STATE_IN_SERVICE) {
+ handleNotInService(ss, tracker.mSentIntent);
} else {
- sendMessage(obtainMessage(EVENT_POST_ALERT, tracker));
+ sendSms(tracker);
}
}
}
/**
- * Post an alert while SMS needs user confirm.
+ * Check if destination is a potential premium short code and sender is not pre-approved to
+ * send to short codes.
*
- * An SmsTracker for the current message.
+ * @param tracker the tracker for the SMS to send
+ * @return true if the destination is approved; false if user confirmation event was sent
*/
- protected void handleReachSentLimit(SmsTracker tracker) {
- if (mSTrackers.size() >= MO_MSG_QUEUE_LIMIT) {
- // Deny the sending when the queue limit is reached.
+ boolean checkDestination(SmsTracker tracker) {
+ if (mUsageMonitor.isApprovedShortCodeSender(tracker.mAppPackage)) {
+ return true; // app is pre-approved to send to short codes
+ } else {
+ String countryIso = mTelephonyManager.getSimCountryIso();
+ if (countryIso == null || countryIso.length() != 2) {
+ Log.e(TAG, "Can't get SIM country code: trying network country code");
+ countryIso = mTelephonyManager.getNetworkCountryIso();
+ }
+
+ switch (mUsageMonitor.checkDestination(tracker.mDestAddress, countryIso)) {
+ case SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE:
+ sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_POSSIBLE_PREMIUM_SHORT_CODE,
+ tracker));
+ return false; // wait for user confirmation before sending
+
+ case SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE:
+ sendMessage(obtainMessage(EVENT_CONFIRM_SEND_TO_PREMIUM_SHORT_CODE,
+ tracker));
+ return false; // wait for user confirmation before sending
+
+ case SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE:
+ case SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE:
+ case SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE:
+ default:
+ return true; // destination is not a premium short code
+ }
+ }
+ }
+
+ /**
+ * Deny sending an SMS if the outgoing queue limit is reached. Used when the message
+ * must be confirmed by the user due to excessive usage or potential premium SMS detected.
+ * @param tracker the SmsTracker for the message to send
+ * @return true if the message was denied; false to continue with send confirmation
+ */
+ private boolean denyIfQueueLimitReached(SmsTracker tracker) {
+ if (mPendingTrackerCount >= MO_MSG_QUEUE_LIMIT) {
+ // Deny sending message when the queue limit is reached.
try {
tracker.mSentIntent.send(RESULT_ERROR_LIMIT_EXCEEDED);
} catch (CanceledException ex) {
Log.e(TAG, "failed to send back RESULT_ERROR_LIMIT_EXCEEDED");
}
- return;
+ return true;
+ }
+ mPendingTrackerCount++;
+ return false;
+ }
+
+ /**
+ * Returns the label for the specified app package name.
+ * @param appPackage the package name of the app requesting to send an SMS
+ * @return the label for the specified app, or the package name if getApplicationInfo() fails
+ */
+ private CharSequence getAppLabel(String appPackage) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo appInfo = pm.getApplicationInfo(appPackage, 0);
+ return appInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "PackageManager Name Not Found for package " + appPackage);
+ return appPackage; // fall back to package name if we can't get app label
+ }
+ }
+
+ /**
+ * Post an alert when SMS needs confirmation due to excessive usage.
+ * @param tracker an SmsTracker for the current message.
+ */
+ protected void handleReachSentLimit(SmsTracker tracker) {
+ if (denyIfQueueLimitReached(tracker)) {
+ return; // queue limit reached; error was returned to caller
}
+ CharSequence appLabel = getAppLabel(tracker.mAppPackage);
Resources r = Resources.getSystem();
+ Spanned messageText = Html.fromHtml(r.getString(R.string.sms_control_message, appLabel));
- String appName = getAppNameByIntent(tracker.mSentIntent);
+ ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
AlertDialog d = new AlertDialog.Builder(mContext)
- .setTitle(r.getString(R.string.sms_control_title))
- .setMessage(appName + " " + r.getString(R.string.sms_control_message))
- .setPositiveButton(r.getString(R.string.sms_control_yes), mListener)
- .setNegativeButton(r.getString(R.string.sms_control_no), mListener)
+ .setTitle(R.string.sms_control_title)
+ .setIcon(R.drawable.stat_sys_warning)
+ .setMessage(messageText)
+ .setPositiveButton(r.getString(R.string.sms_control_yes), listener)
+ .setNegativeButton(r.getString(R.string.sms_control_no), listener)
+ .setOnCancelListener(listener)
.create();
d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
d.show();
-
- mSTrackers.add(tracker);
- sendMessageDelayed ( obtainMessage(EVENT_ALERT_TIMEOUT, d),
- DEFAULT_SMS_TIMEOUT);
}
- protected static String getAppNameByIntent(PendingIntent intent) {
+ /**
+ * Post an alert for user confirmation when sending to a potential short code.
+ * @param isPremium true if the destination is known to be a premium short code
+ * @param tracker the SmsTracker for the current message.
+ */
+ protected void handleConfirmShortCode(boolean isPremium, SmsTracker tracker) {
+ if (denyIfQueueLimitReached(tracker)) {
+ return; // queue limit reached; error was returned to caller
+ }
+
+ int messageId;
+ int titleId;
+ if (isPremium) {
+ messageId = R.string.sms_premium_short_code_confirm_message;
+ titleId = R.string.sms_premium_short_code_confirm_title;
+ } else {
+ messageId = R.string.sms_short_code_confirm_message;
+ titleId = R.string.sms_short_code_confirm_title;
+ }
+
+ CharSequence appLabel = getAppLabel(tracker.mAppPackage);
Resources r = Resources.getSystem();
- return (intent != null) ? intent.getTargetPackage()
- : r.getString(R.string.sms_control_default_app_name);
+ Spanned messageText = Html.fromHtml(r.getString(messageId, appLabel, tracker.mDestAddress));
+
+ ConfirmDialogListener listener = new ConfirmDialogListener(tracker);
+
+ AlertDialog d = new AlertDialog.Builder(mContext)
+ .setTitle(titleId)
+ .setIcon(R.drawable.stat_sys_warning)
+ .setMessage(messageText)
+ .setPositiveButton(r.getString(R.string.sms_short_code_confirm_allow), listener)
+ .setNegativeButton(r.getString(R.string.sms_short_code_confirm_deny), listener)
+// TODO: add third button for "Report malicious app" feature
+// .setNeutralButton(r.getString(R.string.sms_short_code_confirm_report), listener)
+ .setOnCancelListener(listener)
+ .create();
+
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+ d.show();
}
/**
@@ -982,7 +1112,7 @@
if (sentIntents != null && sentIntents.size() > i) {
sentIntent = sentIntents.get(i);
}
- handleNotInService(ss, new SmsTracker(null, sentIntent, null));
+ handleNotInService(ss, sentIntent);
}
return;
}
@@ -1032,12 +1162,17 @@
public final PendingIntent mSentIntent;
public final PendingIntent mDeliveryIntent;
+ public final String mAppPackage;
+ public final String mDestAddress;
+
public SmsTracker(HashMap<String, Object> data, PendingIntent sentIntent,
- PendingIntent deliveryIntent) {
+ PendingIntent deliveryIntent, String appPackage, String destAddr) {
mData = data;
mSentIntent = sentIntent;
mDeliveryIntent = deliveryIntent;
mRetryCount = 0;
+ mAppPackage = appPackage;
+ mDestAddress = destAddr;
}
/**
@@ -1050,19 +1185,35 @@
}
}
- private final DialogInterface.OnClickListener mListener =
- new DialogInterface.OnClickListener() {
+ /**
+ * Dialog listener for SMS confirmation dialog.
+ */
+ private final class ConfirmDialogListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
- public void onClick(DialogInterface dialog, int which) {
- if (which == DialogInterface.BUTTON_POSITIVE) {
- Log.d(TAG, "click YES to send out sms");
- sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS));
- } else if (which == DialogInterface.BUTTON_NEGATIVE) {
- Log.d(TAG, "click NO to stop sending");
- sendMessage(obtainMessage(EVENT_STOP_SENDING));
- }
+ private final SmsTracker mTracker;
+
+ ConfirmDialogListener(SmsTracker tracker) {
+ mTracker = tracker;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ Log.d(TAG, "CONFIRM sending SMS");
+ sendMessage(obtainMessage(EVENT_SEND_CONFIRMED_SMS, mTracker));
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ Log.d(TAG, "DENY sending SMS");
+ sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
}
- };
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ Log.d(TAG, "dialog dismissed: don't send SMS");
+ sendMessage(obtainMessage(EVENT_STOP_SENDING, mTracker));
+ }
+ }
private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
@Override
diff --git a/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
index bd2ae8b..07a0a28 100644
--- a/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
+++ b/telephony/java/com/android/internal/telephony/SmsUsageMonitor.java
@@ -17,13 +17,23 @@
package com.android.internal.telephony;
import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.XmlResourceParser;
import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
import android.util.Log;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.regex.Pattern;
/**
* Implement the per-application based SMS control, which limits the number of
@@ -34,13 +44,28 @@
* dual-mode devices that require support for both 3GPP and 3GPP2 format messages.
*/
public class SmsUsageMonitor {
- private static final String TAG = "SmsStorageMonitor";
+ private static final String TAG = "SmsUsageMonitor";
/** Default checking period for SMS sent without user permission. */
- private static final int DEFAULT_SMS_CHECK_PERIOD = 3600000;
+ private static final int DEFAULT_SMS_CHECK_PERIOD = 1800000; // 30 minutes
/** Default number of SMS sent in checking period without user permission. */
- private static final int DEFAULT_SMS_MAX_COUNT = 100;
+ private static final int DEFAULT_SMS_MAX_COUNT = 30;
+
+ /** Return value from {@link #checkDestination} for regular phone numbers. */
+ static final int CATEGORY_NOT_SHORT_CODE = 0;
+
+ /** Return value from {@link #checkDestination} for free (no cost) short codes. */
+ static final int CATEGORY_FREE_SHORT_CODE = 1;
+
+ /** Return value from {@link #checkDestination} for standard rate (non-premium) short codes. */
+ static final int CATEGORY_STANDARD_SHORT_CODE = 2;
+
+ /** Return value from {@link #checkDestination} for possible premium short codes. */
+ static final int CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE = 3;
+
+ /** Return value from {@link #checkDestination} for premium short codes. */
+ static final int CATEGORY_PREMIUM_SHORT_CODE = 4;
private final int mCheckPeriod;
private final int mMaxAllowed;
@@ -48,10 +73,89 @@
new HashMap<String, ArrayList<Long>>();
/**
- * Create SMS usage monitor.
- * @param resolver the ContentResolver to use to load from secure settings
+ * Hash of package names that are allowed to send to short codes.
+ * TODO: persist this across reboots.
*/
- public SmsUsageMonitor(ContentResolver resolver) {
+ private final HashSet<String> mApprovedShortCodeSenders = new HashSet<String>();
+
+ /** Context for retrieving regexes from XML resource. */
+ private final Context mContext;
+
+ /** Country code for the cached short code pattern matcher. */
+ private String mCurrentCountry;
+
+ /** Cached short code pattern matcher for {@link #mCurrentCountry}. */
+ private ShortCodePatternMatcher mCurrentPatternMatcher;
+
+ /** XML tag for root element. */
+ private static final String TAG_SHORTCODES = "shortcodes";
+
+ /** XML tag for short code patterns for a specific country. */
+ private static final String TAG_SHORTCODE = "shortcode";
+
+ /** XML attribute for the country code. */
+ private static final String ATTR_COUNTRY = "country";
+
+ /** XML attribute for the short code regex pattern. */
+ private static final String ATTR_PATTERN = "pattern";
+
+ /** XML attribute for the premium short code regex pattern. */
+ private static final String ATTR_PREMIUM = "premium";
+
+ /** XML attribute for the free short code regex pattern. */
+ private static final String ATTR_FREE = "free";
+
+ /** XML attribute for the standard rate short code regex pattern. */
+ private static final String ATTR_STANDARD = "standard";
+
+ /**
+ * SMS short code regex pattern matcher for a specific country.
+ */
+ private static final class ShortCodePatternMatcher {
+ private final Pattern mShortCodePattern;
+ private final Pattern mPremiumShortCodePattern;
+ private final Pattern mFreeShortCodePattern;
+ private final Pattern mStandardShortCodePattern;
+
+ ShortCodePatternMatcher(String shortCodeRegex, String premiumShortCodeRegex,
+ String freeShortCodeRegex, String standardShortCodeRegex) {
+ mShortCodePattern = (shortCodeRegex != null ? Pattern.compile(shortCodeRegex) : null);
+ mPremiumShortCodePattern = (premiumShortCodeRegex != null ?
+ Pattern.compile(premiumShortCodeRegex) : null);
+ mFreeShortCodePattern = (freeShortCodeRegex != null ?
+ Pattern.compile(freeShortCodeRegex) : null);
+ mStandardShortCodePattern = (standardShortCodeRegex != null ?
+ Pattern.compile(standardShortCodeRegex) : null);
+ }
+
+ int getNumberCategory(String phoneNumber) {
+ if (mFreeShortCodePattern != null && mFreeShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_FREE_SHORT_CODE;
+ }
+ if (mStandardShortCodePattern != null && mStandardShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_STANDARD_SHORT_CODE;
+ }
+ if (mPremiumShortCodePattern != null && mPremiumShortCodePattern.matcher(phoneNumber)
+ .matches()) {
+ return CATEGORY_PREMIUM_SHORT_CODE;
+ }
+ if (mShortCodePattern != null && mShortCodePattern.matcher(phoneNumber).matches()) {
+ return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+ }
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+ }
+
+ /**
+ * Create SMS usage monitor.
+ * @param context the context to use to load resources and get TelephonyManager service
+ */
+ public SmsUsageMonitor(Context context) {
+ mContext = context;
+ ContentResolver resolver = context.getContentResolver();
+
mMaxAllowed = Settings.Secure.getInt(resolver,
Settings.Secure.SMS_OUTGOING_CHECK_MAX_COUNT,
DEFAULT_SMS_MAX_COUNT);
@@ -59,6 +163,50 @@
mCheckPeriod = Settings.Secure.getInt(resolver,
Settings.Secure.SMS_OUTGOING_CHECK_INTERVAL_MS,
DEFAULT_SMS_CHECK_PERIOD);
+
+ // system MMS app is always allowed to send to short codes
+ mApprovedShortCodeSenders.add("com.android.mms");
+ }
+
+ /**
+ * Return a pattern matcher object for the specified country.
+ * @param country the country to search for
+ * @return a {@link ShortCodePatternMatcher} for the specified country, or null if not found
+ */
+ private ShortCodePatternMatcher getPatternMatcher(String country) {
+ int id = com.android.internal.R.xml.sms_short_codes;
+ XmlResourceParser parser = mContext.getResources().getXml(id);
+
+ try {
+ XmlUtils.beginDocument(parser, TAG_SHORTCODES);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (element.equals(TAG_SHORTCODE)) {
+ String currentCountry = parser.getAttributeValue(null, ATTR_COUNTRY);
+ if (country.equals(currentCountry)) {
+ String pattern = parser.getAttributeValue(null, ATTR_PATTERN);
+ String premium = parser.getAttributeValue(null, ATTR_PREMIUM);
+ String free = parser.getAttributeValue(null, ATTR_FREE);
+ String standard = parser.getAttributeValue(null, ATTR_STANDARD);
+ return new ShortCodePatternMatcher(pattern, premium, free, standard);
+ }
+ } else {
+ Log.e(TAG, "Error: skipping unknown XML tag " + element);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ Log.e(TAG, "XML parser exception reading short code pattern resource", e);
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception reading short code pattern resource", e);
+ } finally {
+ parser.close();
+ }
+ return null; // country not found
}
/** Clear the SMS application list for disposal. */
@@ -67,9 +215,11 @@
}
/**
- * Check to see if an application is allowed to send new SMS messages.
+ * Check to see if an application is allowed to send new SMS messages, and confirm with
+ * user if the send limit was reached or if a non-system app is potentially sending to a
+ * premium SMS short code or number.
*
- * @param appName the application sending sms
+ * @param appName the package name of the app requesting to send an SMS
* @param smsWaiting the number of new messages desired to send
* @return true if application is allowed to send the requested number
* of new sms messages
@@ -89,6 +239,69 @@
}
/**
+ * Return whether the app is approved to send to any short code.
+ * @param appName the package name of the app requesting to send an SMS
+ * @return true if the app is approved; false if we need to confirm short code destinations
+ */
+ public boolean isApprovedShortCodeSender(String appName) {
+ return mApprovedShortCodeSenders.contains(appName);
+ }
+
+ /**
+ * Add app package name to the list of approved short code senders.
+ * @param appName the package name of the app to add
+ */
+ public void addApprovedShortCodeSender(String appName) {
+ Log.d(TAG, "Adding " + appName + " to list of approved short code senders.");
+ mApprovedShortCodeSenders.add(appName);
+ }
+
+ /**
+ * Check if the destination is a possible premium short code.
+ * NOTE: the caller is expected to strip non-digits from the destination number with
+ * {@link PhoneNumberUtils#extractNetworkPortion} before calling this method.
+ * This happens in {@link SMSDispatcher#sendRawPdu} so that we use the same phone number
+ * for testing and in the user confirmation dialog if the user needs to confirm the number.
+ * This makes it difficult for malware to fool the user or the short code pattern matcher
+ * by using non-ASCII characters to make the number appear to be different from the real
+ * destination phone number.
+ *
+ * @param destAddress the destination address to test for possible short code
+ * @return {@link #CATEGORY_NOT_SHORT_CODE}, {@link #CATEGORY_FREE_SHORT_CODE},
+ * {@link #CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE}, or {@link #CATEGORY_PREMIUM_SHORT_CODE}.
+ */
+ public int checkDestination(String destAddress, String countryIso) {
+ // always allow emergency numbers
+ if (PhoneNumberUtils.isEmergencyNumber(destAddress, countryIso)) {
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+
+ ShortCodePatternMatcher patternMatcher = null;
+
+ if (countryIso != null) {
+ if (countryIso.equals(mCurrentCountry)) {
+ patternMatcher = mCurrentPatternMatcher;
+ } else {
+ patternMatcher = getPatternMatcher(countryIso);
+ mCurrentCountry = countryIso;
+ mCurrentPatternMatcher = patternMatcher; // may be null if not found
+ }
+ }
+
+ if (patternMatcher != null) {
+ return patternMatcher.getNumberCategory(destAddress);
+ } else {
+ // Generic rule: numbers of 5 digits or less are considered potential short codes
+ Log.e(TAG, "No patterns for \"" + countryIso + "\": using generic short code rule");
+ if (destAddress.length() <= 5) {
+ return CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+ } else {
+ return CATEGORY_NOT_SHORT_CODE;
+ }
+ }
+ }
+
+ /**
* Remove keys containing only old timestamps. This can happen if an SMS app is used
* to send messages and then uninstalled.
*/
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
index 98a106a0..ff7a0810 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaLteServiceStateTracker.java
@@ -28,6 +28,7 @@
import android.telephony.cdma.CdmaCellLocation;
import android.os.AsyncResult;
import android.os.Message;
+import android.os.SystemProperties;
import android.provider.Telephony.Intents;
import android.text.TextUtils;
@@ -370,14 +371,23 @@
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
ss.getOperatorAlphaLong());
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
operatorNumeric = ss.getOperatorNumeric();
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
if (operatorNumeric == null) {
+ if (DBG) {
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
+ " clear PROPERTY_OPERATOR_ISO_COUNTRY");
+ }
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
mGotCountryCode = false;
} else {
String isoCountryCode = "";
+ String mcc = operatorNumeric.substring(0, 3);
try {
isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(operatorNumeric
.substring(0, 3)));
@@ -386,11 +396,20 @@
} catch (StringIndexOutOfBoundsException ex) {
loge("countryCodeForMcc error" + ex);
}
+ if (DBG) {
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
+ " mcc=" + mcc + " iso-cc=" + isoCountryCode);
+ }
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
isoCountryCode);
mGotCountryCode = true;
- if (mNeedFixZone) {
+
+ // Fix the time zone If the operator changed or we need to fix it because
+ // when the NITZ time came in we didn't know the country code.
+ if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
fixTimeZone(isoCountryCode);
}
}
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index bfa23e7..e6b45f6 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -283,7 +283,7 @@
byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, destPort, data, (deliveryIntent != null));
- sendSubmitPdu(pdu, sentIntent, deliveryIntent);
+ sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
@@ -292,7 +292,7 @@
PendingIntent sentIntent, PendingIntent deliveryIntent) {
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null), null);
- sendSubmitPdu(pdu, sentIntent, deliveryIntent);
+ sendSubmitPdu(pdu, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
@@ -324,11 +324,11 @@
SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destinationAddress,
uData, (deliveryIntent != null) && lastPart);
- sendSubmitPdu(submitPdu, sentIntent, deliveryIntent);
+ sendSubmitPdu(submitPdu, sentIntent, deliveryIntent, destinationAddress);
}
protected void sendSubmitPdu(SmsMessage.SubmitPdu pdu,
- PendingIntent sentIntent, PendingIntent deliveryIntent) {
+ PendingIntent sentIntent, PendingIntent deliveryIntent, String destAddr) {
if (SystemProperties.getBoolean(TelephonyProperties.PROPERTY_INECM_MODE, false)) {
if (sentIntent != null) {
try {
@@ -340,7 +340,7 @@
}
return;
}
- sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent, destAddr);
}
/** {@inheritDoc} */
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 9f27696..b694e0a 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -866,6 +866,12 @@
// If the offset is (0, false) and the time zone property
// is set, use the time zone property rather than GMT.
String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
+ if (DBG) {
+ log("fixTimeZone zoneName='" + zoneName +
+ "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
+ " iso-cc='" + isoCountryCode +
+ "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode));
+ }
if ((mZoneOffset == 0) && (mZoneDst == false) && (zoneName != null)
&& (zoneName.length() > 0)
&& (Arrays.binarySearch(GMT_COUNTRY_CODES, isoCountryCode) < 0)) {
@@ -880,19 +886,25 @@
// Adjust the saved NITZ time to account for tzOffset.
mSavedTime = mSavedTime - tzOffset;
}
+ if (DBG) log("fixTimeZone: using default TimeZone");
} else if (isoCountryCode.equals("")) {
// Country code not found. This is likely a test network.
// Get a TimeZone based only on the NITZ parameters (best guess).
zone = getNitzTimeZone(mZoneOffset, mZoneDst, mZoneTime);
+ if (DBG) log("fixTimeZone: using NITZ TimeZone");
} else {
zone = TimeUtils.getTimeZone(mZoneOffset, mZoneDst, mZoneTime, isoCountryCode);
+ if (DBG) log("fixTimeZone: using getTimeZone(off, dst, time, iso)");
}
mNeedFixZone = false;
if (zone != null) {
+ log("fixTimeZone: zone != null zone.getID=" + zone.getID());
if (getAutoTimeZone()) {
setAndBroadcastNetworkSetTimeZone(zone.getID());
+ } else {
+ log("fixTimeZone: zone == null");
}
saveNitzTimeZone(zone.getID());
}
@@ -985,14 +997,23 @@
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
ss.getOperatorAlphaLong());
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
operatorNumeric = ss.getOperatorNumeric();
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
if (operatorNumeric == null) {
+ if (DBG) {
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
+ " clear PROPERTY_OPERATOR_ISO_COUNTRY");
+ }
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
mGotCountryCode = false;
} else {
String isoCountryCode = "";
+ String mcc = operatorNumeric.substring(0, 3);
try{
isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(
operatorNumeric.substring(0,3)));
@@ -1001,11 +1022,20 @@
} catch ( StringIndexOutOfBoundsException ex) {
loge("pollStateDone: countryCodeForMcc error" + ex);
}
+ if (DBG) {
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
+ " mcc=" + mcc + " iso-cc=" + isoCountryCode);
+ }
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY,
isoCountryCode);
mGotCountryCode = true;
- if (mNeedFixZone) {
+
+ // Fix the time zone If the operator changed or we need to fix it because
+ // when the NITZ time came in we didn't know the country code.
+ if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
fixTimeZone(isoCountryCode);
}
}
@@ -1316,7 +1346,6 @@
String iso = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY);
if (zone == null) {
-
if (mGotCountryCode) {
if (iso != null && iso.length() > 0) {
zone = TimeUtils.getTimeZone(tzOffset, dst != 0,
@@ -1332,16 +1361,21 @@
}
}
- if (zone == null) {
- // We got the time before the country, so we don't know
- // how to identify the DST rules yet. Save the information
- // and hope to fix it up later.
+ if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+ // We got the time before the country or the zone has changed
+ // so we don't know how to identify the DST rules yet. Save
+ // the information and hope to fix it up later.
mNeedFixZone = true;
mZoneOffset = tzOffset;
mZoneDst = dst != 0;
mZoneTime = c.getTimeInMillis();
}
+ if (DBG) {
+ log("NITZ: tzOffset=" + tzOffset + " dst=" + dst + " zone=" + zone.getID() +
+ " iso=" + iso + " mGotCountryCode=" + mGotCountryCode +
+ " mNeedFixZone=" + mNeedFixZone);
+ }
if (zone != null) {
if (getAutoTimeZone()) {
@@ -1461,6 +1495,7 @@
* @param zoneId timezone set by carrier
*/
private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+ if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
AlarmManager alarm =
(AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
alarm.setTimeZone(zoneId);
@@ -1477,6 +1512,7 @@
* @param time time set by network
*/
private void setAndBroadcastNetworkSetTime(long time) {
+ if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
SystemClock.setCurrentTimeMillis(time);
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 931c662..bfa2bb1 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -241,7 +241,8 @@
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, destPort, data, (deliveryIntent != null));
if (pdu != null) {
- sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destAddr);
} else {
Log.e(TAG, "GsmSMSDispatcher.sendData(): getSubmitPdu() returned null");
}
@@ -254,7 +255,8 @@
SmsMessage.SubmitPdu pdu = SmsMessage.getSubmitPdu(
scAddr, destAddr, text, (deliveryIntent != null));
if (pdu != null) {
- sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destAddr);
} else {
Log.e(TAG, "GsmSMSDispatcher.sendText(): getSubmitPdu() returned null");
}
@@ -276,7 +278,8 @@
message, deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
encoding, smsHeader.languageTable, smsHeader.languageShiftTable);
if (pdu != null) {
- sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent);
+ sendRawPdu(pdu.encodedScAddress, pdu.encodedMessage, sentIntent, deliveryIntent,
+ destinationAddress);
} else {
Log.e(TAG, "GsmSMSDispatcher.sendNewSubmitPdu(): getSubmitPdu() returned null");
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
index 662f1f6..c0acf5b 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmServiceStateTracker.java
@@ -852,12 +852,16 @@
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA,
ss.getOperatorAlphaLong());
+ String prevOperatorNumeric =
+ SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
operatorNumeric = ss.getOperatorNumeric();
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric);
if (operatorNumeric == null) {
if (DBG) {
- log("pollStateDone: operatorNumeric is null:" +
+ log("pollStateDone: operatorNumeric=" + operatorNumeric +
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
" clear PROPERTY_OPERATOR_ISO_COUNTRY");
}
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, "");
@@ -875,7 +879,9 @@
}
if (DBG) {
log("pollStateDone: operatorNumeric=" + operatorNumeric +
- " mcc=" + mcc + " iso=" + iso);
+ " prevOperatorNumeric=" + prevOperatorNumeric +
+ " mNeedFixZone=" + mNeedFixZone +
+ " mcc=" + mcc + " iso-cc=" + iso);
}
phone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, iso);
@@ -895,7 +901,7 @@
if ((uniqueZones.size() == 1) || testOneUniqueOffsetPath) {
zone = uniqueZones.get(0);
if (DBG) {
- log("pollStateDone: no nitz but one TZ for iso=" + iso +
+ log("pollStateDone: no nitz but one TZ for iso-cc=" + iso +
" with zone.getID=" + zone.getID() +
" testOneUniqueOffsetPath=" + testOneUniqueOffsetPath);
}
@@ -903,22 +909,24 @@
} else {
if (DBG) {
log("pollStateDone: there are " + uniqueZones.size() +
- " unique offsets for iso='" + iso +
+ " unique offsets for iso-cc='" + iso +
" testOneUniqueOffsetPath=" + testOneUniqueOffsetPath +
"', do nothing");
}
}
}
- if (mNeedFixZone) {
+ // Fix the time zone If the operator changed or we need to fix it because
+ // when the NITZ time came in we didn't know the country code.
+ if ( ! operatorNumeric.equals(prevOperatorNumeric) || mNeedFixZone) {
// If the offset is (0, false) and the timezone property
// is set, use the timezone property rather than
// GMT.
String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
if (DBG) {
- log("pollStateDone: mNeedFixZone==true zoneName='" + zoneName +
+ log("pollStateDone: fix time zone zoneName='" + zoneName +
"' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
- " iso='" + iso +
+ " iso-cc='" + iso +
"' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
}
if ((mZoneOffset == 0) && (mZoneDst == false) &&
@@ -957,15 +965,6 @@
} else {
log("pollStateDone: zone == null");
}
- } else {
- if (DBG) {
- String zoneName = SystemProperties.get(TIMEZONE_PROPERTY);
- zone = TimeZone.getDefault();
- log("pollStateDone: mNeedFixZone==false zoneName='" + zoneName +
- "' mZoneOffset=" + mZoneOffset + " mZoneDst=" + mZoneDst +
- " iso='" + iso +
- "' iso-cc-idx=" + Arrays.binarySearch(GMT_COUNTRY_CODES, iso));
- }
}
}
@@ -1442,10 +1441,10 @@
}
}
- if (zone == null) {
- // We got the time before the country, so we don't know
- // how to identify the DST rules yet. Save the information
- // and hope to fix it up later.
+ if ((zone == null) || (mZoneOffset != tzOffset) || (mZoneDst != (dst != 0))){
+ // We got the time before the country or the zone has changed
+ // so we don't know how to identify the DST rules yet. Save
+ // the information and hope to fix it up later.
mNeedFixZone = true;
mZoneOffset = tzOffset;
@@ -1556,6 +1555,7 @@
* @param zoneId timezone set by carrier
*/
private void setAndBroadcastNetworkSetTimeZone(String zoneId) {
+ if (DBG) log("setAndBroadcastNetworkSetTimeZone: setTimeZone=" + zoneId);
AlarmManager alarm =
(AlarmManager) phone.getContext().getSystemService(Context.ALARM_SERVICE);
alarm.setTimeZone(zoneId);
@@ -1576,6 +1576,7 @@
* @param time time set by network
*/
private void setAndBroadcastNetworkSetTime(long time) {
+ if (DBG) log("setAndBroadcastNetworkSetTime: time=" + time + "ms");
SystemClock.setCurrentTimeMillis(time);
Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
new file mode 100644
index 0000000..3bb7c06
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/SmsUsageMonitorShortCodeTest.java
@@ -0,0 +1,466 @@
+/*
+ * 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_FREE_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_NOT_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_PREMIUM_SHORT_CODE;
+import static com.android.internal.telephony.SmsUsageMonitor.CATEGORY_STANDARD_SHORT_CODE;
+
+/**
+ * Test cases for SMS short code pattern matching in SmsUsageMonitor.
+ */
+public class SmsUsageMonitorShortCodeTest extends AndroidTestCase {
+
+ private static final class ShortCodeTest {
+ final String countryIso;
+ final String address;
+ final int category;
+
+ ShortCodeTest(String countryIso, String destAddress, int category) {
+ this.countryIso = countryIso;
+ this.address = destAddress;
+ this.category = category;
+ }
+ }
+
+ /**
+ * List of short code test cases.
+ */
+ private static final ShortCodeTest[] sShortCodeTests = new ShortCodeTest[] {
+ new ShortCodeTest("al", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("al", "4321", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("al", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("al", "15191", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("al", "55500", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("al", "55600", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("al", "654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("am", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("am", "101", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("am", "102", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("am", "103", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("am", "222", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "1111", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "9999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "1121", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "1141", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("am", "3024", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("at", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("at", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("at", "0901234", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("at", "0900666266", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("au", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("au", "180000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("au", "190000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("au", "1900000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("au", "19000000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("au", "19998882", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("az", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("az", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "87744", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "3301", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "3302", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "9012", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "9014", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "9394", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "87744", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "93101", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("az", "123456", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("be", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("be", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("be", "567890", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("be", "8000", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("be", "6566", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("be", "7777", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("bg", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("bg", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("bg", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "1816", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "1915", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "1916", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "1935", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("bg", "18423", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("by", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("by", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("by", "3336", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("by", "5013", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("by", "5014", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("by", "7781", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("ca", "911", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ca", "+18005551234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ca", "8005551234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ca", "20000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ca", "200000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ca", "2000000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ca", "60999", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ca", "88188", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("ch", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ch", "123", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ch", "234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "3456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "98765", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "543", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "83111", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ch", "234567", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ch", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("cn", "120", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("cn", "1062503000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("cn", "1065123456", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("cn", "1066335588", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("cy", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("cy", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("cy", "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cy", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cy", "654321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cy", "7510", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cy", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("cz", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("cz", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("cz", "9090150", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cz", "90901599", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("cz", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("de", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("de", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("de", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "8888", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "11111", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "11886", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "22022", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "23300", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "3434", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "34567", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "41414", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "55655", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "66766", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "66777", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "77677", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "80888", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "1232286", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("de", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("dk", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("dk", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("dk", "1259", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("dk", "16123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("dk", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ee", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ee", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("ee", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "1259", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "15330", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "17999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "17010", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "17013", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "9034567", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ee", "34567890", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("es", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("es", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("es", "25165", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("es", "27333", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("es", "995399", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("es", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("fi", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("fi", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("fi", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "123456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "17159", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "17163", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "0600123", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "070012345", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fi", "987654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("fr", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("fr", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("fr", "34567", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("fr", "45678", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fr", "81185", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("fr", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("gb", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("gb", "999", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("gb", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("gb", "4567", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "45678", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "56789", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "79067", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "80079", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "654321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gb", "7654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ge", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ge", "8765", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ge", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ge", "8012", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ge", "8013", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ge", "8014", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ge", "8889", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("gr", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("gr", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("gr", "54321", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gr", "19567", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gr", "19678", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("gr", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("hu", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("hu", "012", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "0123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "1784", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "2345", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "01234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "012345678", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "0123456789", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "1234567890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "0691227910", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("hu", "2345678901", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("hu", "01234567890", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ie", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ie", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("ie", "50123", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("ie", "51234", CATEGORY_STANDARD_SHORT_CODE),
+ new ShortCodeTest("ie", "52345", CATEGORY_STANDARD_SHORT_CODE),
+ new ShortCodeTest("ie", "57890", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ie", "67890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ie", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("il", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("il", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("il", "4422", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("il", "4545", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("il", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("it", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("it", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("it", "4567", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("it", "48000", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("it", "45678", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("it", "56789", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("it", "456789", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("kg", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("kg", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kg", "4152", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kg", "4157", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kg", "4449", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kg", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("kz", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("kz", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kz", "9194", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kz", "7790", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("kz", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("lt", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("lt", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("lt", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "1381", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "1394", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "1645", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "12345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lt", "123456", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("lu", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("lu", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("lu", "1234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("lu", "12345", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("lu", "64747", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lu", "678901", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("lv", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("lv", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("lv", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lv", "1819", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lv", "1863", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lv", "1874", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("lv", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("mx", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("mx", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("mx", "7766", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("mx", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("mx", "53035", CATEGORY_PREMIUM_SHORT_CODE),
+
+ new ShortCodeTest("my", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("my", "1234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("my", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("my", "32298", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("my", "33776", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("my", "345678", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("nl", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("nl", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("nl", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nl", "4466", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nl", "5040", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nl", "23456", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("no", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("no", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("no", "2201", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("no", "2226", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("no", "2227", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("no", "23456", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("no", "234567", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("nz", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("nz", "123", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nz", "2345", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nz", "3903", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nz", "8995", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("nz", "23456", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("pl", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("pl", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("pl", "7890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "34567", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "7910", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "74240", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "79866", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "92525", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pl", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("pt", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("pt", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("pt", "61000", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pt", "62345", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pt", "68304", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pt", "69876", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("pt", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ro", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ro", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("ro", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "1263", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "1288", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "1314", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "1380", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "7890", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ro", "12345", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ru", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ru", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ru", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ru", "2097", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ru", "3933", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ru", "7781", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ru", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("se", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("se", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("se", "1234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("se", "72345", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("se", "72999", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("se", "123456", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("se", "87654321", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("sg", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("sg", "1234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("sg", "70000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sg", "79999", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sg", "73800", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sg", "74688", CATEGORY_STANDARD_SHORT_CODE),
+ new ShortCodeTest("sg", "987654", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("si", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("si", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("si", "1234", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("si", "3838", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("si", "72999", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("sk", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("sk", "116117", CATEGORY_FREE_SHORT_CODE),
+ new ShortCodeTest("sk", "1234", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sk", "6674", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sk", "7604", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("sk", "72999", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("tj", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("tj", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("tj", "1161", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("tj", "1171", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("tj", "4161", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("tj", "4449", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("tj", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("ua", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("ua", "5432", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ua", "4448", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ua", "7094", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ua", "7540", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("ua", "98765", CATEGORY_NOT_SHORT_CODE),
+
+ new ShortCodeTest("us", "911", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("us", "+18005551234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("us", "8005551234", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("us", "20000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("us", "200000", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("us", "2000000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("us", "20433", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("us", "21472", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("us", "23333", CATEGORY_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("us", "99807", CATEGORY_PREMIUM_SHORT_CODE),
+
+ // generic rules for other countries: 5 digits or less considered potential short code
+ new ShortCodeTest("zz", "2000000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest("zz", "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("zz", "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("zz", "321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest("zz", "112", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest(null, "2000000", CATEGORY_NOT_SHORT_CODE),
+ new ShortCodeTest(null, "54321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest(null, "4321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest(null, "321", CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE),
+ new ShortCodeTest(null, "112", CATEGORY_NOT_SHORT_CODE),
+ };
+
+ @SmallTest
+ public void testSmsUsageMonitor() {
+ SmsUsageMonitor monitor = new SmsUsageMonitor(getContext());
+ for (ShortCodeTest test : sShortCodeTests) {
+ assertEquals("country: " + test.countryIso + " number: " + test.address,
+ test.category, monitor.checkDestination(test.address, test.countryIso));
+ }
+ }
+}
diff --git a/test-runner/src/android/test/AssertionFailedError.java b/test-runner/src/android/test/AssertionFailedError.java
index 7af5806..b3ac6d1 100644
--- a/test-runner/src/android/test/AssertionFailedError.java
+++ b/test-runner/src/android/test/AssertionFailedError.java
@@ -19,8 +19,7 @@
/**
* Thrown when an assertion failed.
*
- * Note: Most users of this class should simply use junit.framework.AssertionFailedError,
- * which provides the same functionality.
+ * @deprecated use junit.framework.AssertionFailedError
*/
public class AssertionFailedError extends Error {
diff --git a/test-runner/src/android/test/ComparisonFailure.java b/test-runner/src/android/test/ComparisonFailure.java
index e7e9698..3fa76f5 100644
--- a/test-runner/src/android/test/ComparisonFailure.java
+++ b/test-runner/src/android/test/ComparisonFailure.java
@@ -19,8 +19,7 @@
/**
* Thrown when an assert equals for Strings failed.
*
- * Note: Most users of this class should simply use junit.framework.ComparisonFailure,
- * which provides the same functionality at a lighter weight.
+ * @deprecated use junit.framework.ComparisonFailure
*/
public class ComparisonFailure extends AssertionFailedError {
private junit.framework.ComparisonFailure mComparison;
diff --git a/test-runner/src/junit/runner/BaseTestRunner.java b/test-runner/src/junit/runner/BaseTestRunner.java
index e073ef7..8cfd7fa 100644
--- a/test-runner/src/junit/runner/BaseTestRunner.java
+++ b/test-runner/src/junit/runner/BaseTestRunner.java
@@ -1,10 +1,24 @@
package junit.runner;
-import junit.framework.*;
-import java.lang.reflect.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.text.NumberFormat;
-import java.io.*;
-import java.util.*;
+import java.util.Properties;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestListener;
+import junit.framework.TestSuite;
/**
* Base class for all test runners.
@@ -19,8 +33,8 @@
boolean fLoading= true;
/*
- * Implementation of TestListener
- */
+ * Implementation of TestListener
+ */
public synchronized void startTest(Test test) {
testStarted(test.toString());
}
@@ -32,9 +46,9 @@
protected static Properties getPreferences() {
if (fPreferences == null) {
fPreferences= new Properties();
- fPreferences.put("loading", "true");
- fPreferences.put("filterstack", "true");
- readPreferences();
+ fPreferences.put("loading", "true");
+ fPreferences.put("filterstack", "true");
+ readPreferences();
}
return fPreferences;
}
@@ -48,8 +62,9 @@
}
}
+ // android-changed remove 'static' qualifier for API compatibility
public void setPreference(String key, String value) {
- getPreferences().setProperty(key, value);
+ getPreferences().put(key, value);
}
public synchronized void endTest(Test test) {
@@ -97,8 +112,8 @@
Method suiteMethod= null;
try {
suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
- } catch(Exception e) {
- // try to extract a test suite automatically
+ } catch(Exception e) {
+ // try to extract a test suite automatically
clearStatus();
return new TestSuite(testClass);
}
@@ -108,7 +123,7 @@
}
Test test= null;
try {
- test= (Test)suiteMethod.invoke(null); // static method
+ test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method
if (test == null)
return test;
}
@@ -163,7 +178,7 @@
fLoading= enable;
}
/**
- * Extract the class name from a String
+ * Extract the class name from a String in VA/Java style
*/
public String extractClassName(String className) {
if(className.startsWith("Default package for"))
@@ -186,11 +201,24 @@
*/
protected abstract void runFailed(String message);
+ // BEGIN android-changed - add back getLoader() for API compatibility
+ /**
+ * Returns the loader to be used.
+ *
+ * @deprecated not present in JUnit4.10
+ */
+ public TestSuiteLoader getLoader() {
+ if (useReloadingTestSuiteLoader())
+ return new ReloadingTestSuiteLoader();
+ return new StandardTestSuiteLoader();
+ }
+ // END android-changed
+
/**
* Returns the loaded Class for a suite name.
*/
- protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
- return getLoader().load(suiteClassName);
+ protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
+ return Class.forName(suiteClassName);
}
/**
@@ -199,29 +227,20 @@
protected void clearStatus() { // Belongs in the GUI TestRunner class
}
- /**
- * Returns the loader to be used.
- */
- public TestSuiteLoader getLoader() {
- if (useReloadingTestSuiteLoader())
- return new ReloadingTestSuiteLoader();
- return new StandardTestSuiteLoader();
- }
-
protected boolean useReloadingTestSuiteLoader() {
- return getPreference("loading").equals("true") && !inVAJava() && fLoading;
+ return getPreference("loading").equals("true") && fLoading;
}
private static File getPreferencesFile() {
- String home= System.getProperty("user.home");
- return new File(home, "junit.properties");
- }
+ String home= System.getProperty("user.home");
+ return new File(home, "junit.properties");
+ }
- private static void readPreferences() {
- InputStream is= null;
- try {
- is= new FileInputStream(getPreferencesFile());
- setPreferences(new Properties(getPreferences()));
+ private static void readPreferences() {
+ InputStream is= null;
+ try {
+ is= new FileInputStream(getPreferencesFile());
+ setPreferences(new Properties(getPreferences()));
getPreferences().load(is);
} catch (IOException e) {
try {
@@ -230,32 +249,22 @@
} catch (IOException e1) {
}
}
- }
+ }
- public static String getPreference(String key) {
- return getPreferences().getProperty(key);
- }
+ public static String getPreference(String key) {
+ return getPreferences().getProperty(key);
+ }
- public static int getPreference(String key, int dflt) {
- String value= getPreference(key);
- int intValue= dflt;
- if (value == null)
- return intValue;
- try {
- intValue= Integer.parseInt(value);
- } catch (NumberFormatException ne) {
- }
- return intValue;
- }
-
- public static boolean inVAJava() {
+ public static int getPreference(String key, int dflt) {
+ String value= getPreference(key);
+ int intValue= dflt;
+ if (value == null)
+ return intValue;
try {
- Class.forName("com.ibm.uvm.tools.DebugSupport");
+ intValue= Integer.parseInt(value);
+ } catch (NumberFormatException ne) {
}
- catch (Exception e) {
- return false;
- }
- return true;
+ return intValue;
}
/**
@@ -270,6 +279,13 @@
return BaseTestRunner.getFilteredTrace(trace);
}
+ // BEGIN android-changed - add back this method for API compatibility
+ /** @deprecated not present in JUnit4.10 */
+ public static boolean inVAJava() {
+ return false;
+ }
+ // END android-changed
+
/**
* Filters stack frames from internal JUnit classes
*/
@@ -303,14 +319,14 @@
static boolean filterLine(String line) {
String[] patterns= new String[] {
- "junit.framework.TestCase",
- "junit.framework.TestResult",
- "junit.framework.TestSuite",
- "junit.framework.Assert.", // don't filter AssertionFailure
- "junit.swingui.TestRunner",
- "junit.awtui.TestRunner",
- "junit.textui.TestRunner",
- "java.lang.reflect.Method.invoke("
+ "junit.framework.TestCase",
+ "junit.framework.TestResult",
+ "junit.framework.TestSuite",
+ "junit.framework.Assert.", // don't filter AssertionFailure
+ "junit.swingui.TestRunner",
+ "junit.awtui.TestRunner",
+ "junit.textui.TestRunner",
+ "java.lang.reflect.Method.invoke("
};
for (int i= 0; i < patterns.length; i++) {
if (line.indexOf(patterns[i]) > 0)
@@ -319,8 +335,8 @@
return false;
}
- static {
- fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
- }
+ static {
+ fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
+ }
}
diff --git a/test-runner/src/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java
index 8a3c702..f48ddee 100644
--- a/test-runner/src/junit/runner/ClassPathTestCollector.java
+++ b/test-runner/src/junit/runner/ClassPathTestCollector.java
@@ -13,69 +13,69 @@
* {@hide} - Not needed for 1.0 SDK
*/
public abstract class ClassPathTestCollector implements TestCollector {
-
- static final int SUFFIX_LENGTH= ".class".length();
-
- public ClassPathTestCollector() {
- }
-
- public Enumeration collectTests() {
- String classPath= System.getProperty("java.class.path");
- Hashtable result = collectFilesInPath(classPath);
- return result.elements();
- }
- public Hashtable collectFilesInPath(String classPath) {
- Hashtable result= collectFilesInRoots(splitClassPath(classPath));
- return result;
- }
-
- Hashtable collectFilesInRoots(Vector roots) {
- Hashtable result= new Hashtable(100);
- Enumeration e= roots.elements();
- while (e.hasMoreElements())
- gatherFiles(new File((String)e.nextElement()), "", result);
- return result;
- }
+ static final int SUFFIX_LENGTH= ".class".length();
- void gatherFiles(File classRoot, String classFileName, Hashtable result) {
- File thisRoot= new File(classRoot, classFileName);
- if (thisRoot.isFile()) {
- if (isTestClass(classFileName)) {
- String className= classNameFromFile(classFileName);
- result.put(className, className);
- }
- return;
- }
- String[] contents= thisRoot.list();
- if (contents != null) {
- for (int i= 0; i < contents.length; i++)
- gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
- }
- }
-
- Vector splitClassPath(String classPath) {
- Vector result= new Vector();
- String separator= System.getProperty("path.separator");
- StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
- while (tokenizer.hasMoreTokens())
- result.addElement(tokenizer.nextToken());
- return result;
- }
-
- protected boolean isTestClass(String classFileName) {
- return
- classFileName.endsWith(".class") &&
- classFileName.indexOf('$') < 0 &&
- classFileName.indexOf("Test") > 0;
- }
-
- protected String classNameFromFile(String classFileName) {
- // convert /a/b.class to a.b
- String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
- String s2= s.replace(File.separatorChar, '.');
- if (s2.startsWith("."))
- return s2.substring(1);
- return s2;
- }
+ public ClassPathTestCollector() {
+ }
+
+ public Enumeration collectTests() {
+ String classPath= System.getProperty("java.class.path");
+ Hashtable result = collectFilesInPath(classPath);
+ return result.elements();
+ }
+
+ public Hashtable collectFilesInPath(String classPath) {
+ Hashtable result= collectFilesInRoots(splitClassPath(classPath));
+ return result;
+ }
+
+ Hashtable collectFilesInRoots(Vector roots) {
+ Hashtable result= new Hashtable(100);
+ Enumeration e= roots.elements();
+ while (e.hasMoreElements())
+ gatherFiles(new File((String)e.nextElement()), "", result);
+ return result;
+ }
+
+ void gatherFiles(File classRoot, String classFileName, Hashtable result) {
+ File thisRoot= new File(classRoot, classFileName);
+ if (thisRoot.isFile()) {
+ if (isTestClass(classFileName)) {
+ String className= classNameFromFile(classFileName);
+ result.put(className, className);
+ }
+ return;
+ }
+ String[] contents= thisRoot.list();
+ if (contents != null) {
+ for (int i= 0; i < contents.length; i++)
+ gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
+ }
+ }
+
+ Vector splitClassPath(String classPath) {
+ Vector result= new Vector();
+ String separator= System.getProperty("path.separator");
+ StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
+ while (tokenizer.hasMoreTokens())
+ result.addElement(tokenizer.nextToken());
+ return result;
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
+
+ protected String classNameFromFile(String classFileName) {
+ // convert /a/b.class to a.b
+ String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
+ String s2= s.replace(File.separatorChar, '.');
+ if (s2.startsWith("."))
+ return s2.substring(1);
+ return s2;
+ }
}
diff --git a/test-runner/src/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java
index 7108cec..1b8365a 100644
--- a/test-runner/src/junit/runner/FailureDetailView.java
+++ b/test-runner/src/junit/runner/FailureDetailView.java
@@ -1,7 +1,7 @@
package junit.runner;
// The following line was removed for compatibility with Android libraries.
-//import java.awt.Component;
+//import java.awt.Component;
import junit.framework.*;
@@ -17,12 +17,12 @@
// */
// public Component getComponent();
- /**
- * Shows details of a TestFailure
- */
- public void showFailure(TestFailure failure);
- /**
- * Clears the view
- */
- public void clear();
+ /**
+ * Shows details of a TestFailure
+ */
+ public void showFailure(TestFailure failure);
+ /**
+ * Clears the view
+ */
+ public void clear();
}
diff --git a/test-runner/src/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java
index b1760b1..489d9d6 100644
--- a/test-runner/src/junit/runner/LoadingTestCollector.java
+++ b/test-runner/src/junit/runner/LoadingTestCollector.java
@@ -12,59 +12,59 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class LoadingTestCollector extends ClassPathTestCollector {
-
- TestCaseClassLoader fLoader;
-
- public LoadingTestCollector() {
- fLoader= new TestCaseClassLoader();
- }
-
- protected boolean isTestClass(String classFileName) {
- try {
- if (classFileName.endsWith(".class")) {
- Class testClass= classFromFile(classFileName);
- return (testClass != null) && isTestClass(testClass);
- }
- }
- catch (ClassNotFoundException expected) {
- }
- catch (NoClassDefFoundError notFatal) {
- }
- return false;
- }
-
- Class classFromFile(String classFileName) throws ClassNotFoundException {
- String className= classNameFromFile(classFileName);
- if (!fLoader.isExcluded(className))
- return fLoader.loadClass(className, false);
- return null;
- }
-
- boolean isTestClass(Class testClass) {
- if (hasSuiteMethod(testClass))
- return true;
- if (Test.class.isAssignableFrom(testClass) &&
- Modifier.isPublic(testClass.getModifiers()) &&
- hasPublicConstructor(testClass))
- return true;
- return false;
- }
-
- boolean hasSuiteMethod(Class testClass) {
- try {
- testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
- } catch(Exception e) {
- return false;
- }
- return true;
- }
-
- boolean hasPublicConstructor(Class testClass) {
- try {
- TestSuite.getTestConstructor(testClass);
- } catch(NoSuchMethodException e) {
- return false;
- }
- return true;
- }
+
+ TestCaseClassLoader fLoader;
+
+ public LoadingTestCollector() {
+ fLoader= new TestCaseClassLoader();
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ try {
+ if (classFileName.endsWith(".class")) {
+ Class testClass= classFromFile(classFileName);
+ return (testClass != null) && isTestClass(testClass);
+ }
+ }
+ catch (ClassNotFoundException expected) {
+ }
+ catch (NoClassDefFoundError notFatal) {
+ }
+ return false;
+ }
+
+ Class classFromFile(String classFileName) throws ClassNotFoundException {
+ String className= classNameFromFile(classFileName);
+ if (!fLoader.isExcluded(className))
+ return fLoader.loadClass(className, false);
+ return null;
+ }
+
+ boolean isTestClass(Class testClass) {
+ if (hasSuiteMethod(testClass))
+ return true;
+ if (Test.class.isAssignableFrom(testClass) &&
+ Modifier.isPublic(testClass.getModifiers()) &&
+ hasPublicConstructor(testClass))
+ return true;
+ return false;
+ }
+
+ boolean hasSuiteMethod(Class testClass) {
+ try {
+ testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
+ } catch(Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ boolean hasPublicConstructor(Class testClass) {
+ try {
+ TestSuite.getTestConstructor(testClass);
+ } catch(NoSuchMethodException e) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
index a6d84fe..c4d80d0 100644
--- a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
@@ -5,16 +5,16 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class ReloadingTestSuiteLoader implements TestSuiteLoader {
-
- public Class load(String suiteClassName) throws ClassNotFoundException {
- return createLoader().loadClass(suiteClassName, true);
- }
-
- public Class reload(Class aClass) throws ClassNotFoundException {
- return createLoader().loadClass(aClass.getName(), true);
- }
-
- protected TestCaseClassLoader createLoader() {
- return new TestCaseClassLoader();
- }
+
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return createLoader().loadClass(suiteClassName, true);
+ }
+
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return createLoader().loadClass(aClass.getName(), true);
+ }
+
+ protected TestCaseClassLoader createLoader() {
+ return new TestCaseClassLoader();
+ }
}
diff --git a/test-runner/src/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java
index 543168f..6cb0e19 100644
--- a/test-runner/src/junit/runner/SimpleTestCollector.java
+++ b/test-runner/src/junit/runner/SimpleTestCollector.java
@@ -8,14 +8,14 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class SimpleTestCollector extends ClassPathTestCollector {
-
- public SimpleTestCollector() {
- }
-
- protected boolean isTestClass(String classFileName) {
- return
- classFileName.endsWith(".class") &&
- classFileName.indexOf('$') < 0 &&
- classFileName.indexOf("Test") > 0;
- }
+
+ public SimpleTestCollector() {
+ }
+
+ protected boolean isTestClass(String classFileName) {
+ return
+ classFileName.endsWith(".class") &&
+ classFileName.indexOf('$') < 0 &&
+ classFileName.indexOf("Test") > 0;
+ }
}
diff --git a/test-runner/src/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java
index 66f551e..7731f66 100644
--- a/test-runner/src/junit/runner/Sorter.java
+++ b/test-runner/src/junit/runner/Sorter.java
@@ -11,29 +11,29 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class Sorter {
- public static interface Swapper {
- public void swap(Vector values, int left, int right);
- }
-
- public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
- int oleft= left;
- int oright= right;
- String mid= (String)values.elementAt((left + right) / 2);
- do {
- while (((String)(values.elementAt(left))).compareTo(mid) < 0)
- left++;
- while (mid.compareTo((String)(values.elementAt(right))) < 0)
- right--;
- if (left <= right) {
- swapper.swap(values, left, right);
- left++;
- right--;
- }
- } while (left <= right);
-
- if (oleft < right)
- sortStrings(values, oleft, right, swapper);
- if (left < oright)
- sortStrings(values, left, oright, swapper);
- }
+ public static interface Swapper {
+ public void swap(Vector values, int left, int right);
+ }
+
+ public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
+ int oleft= left;
+ int oright= right;
+ String mid= (String)values.elementAt((left + right) / 2);
+ do {
+ while (((String)(values.elementAt(left))).compareTo(mid) < 0)
+ left++;
+ while (mid.compareTo((String)(values.elementAt(right))) < 0)
+ right--;
+ if (left <= right) {
+ swapper.swap(values, left, right);
+ left++;
+ right--;
+ }
+ } while (left <= right);
+
+ if (oleft < right)
+ sortStrings(values, oleft, right, swapper);
+ if (left < oright)
+ sortStrings(values, left, oright, swapper);
+ }
}
diff --git a/test-runner/src/junit/runner/StandardTestSuiteLoader.java b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
index bce7dec..381e684 100644
--- a/test-runner/src/junit/runner/StandardTestSuiteLoader.java
+++ b/test-runner/src/junit/runner/StandardTestSuiteLoader.java
@@ -5,16 +5,16 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class StandardTestSuiteLoader implements TestSuiteLoader {
- /**
- * Uses the system class loader to load the test class
- */
- public Class load(String suiteClassName) throws ClassNotFoundException {
- return Class.forName(suiteClassName);
- }
- /**
- * Uses the system class loader to load the test class
- */
- public Class reload(Class aClass) throws ClassNotFoundException {
- return aClass;
- }
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class load(String suiteClassName) throws ClassNotFoundException {
+ return Class.forName(suiteClassName);
+ }
+ /**
+ * Uses the system class loader to load the test class
+ */
+ public Class reload(Class aClass) throws ClassNotFoundException {
+ return aClass;
+ }
}
diff --git a/test-runner/src/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java
index 3a510c6..09eec7f 100644
--- a/test-runner/src/junit/runner/TestCaseClassLoader.java
+++ b/test-runner/src/junit/runner/TestCaseClassLoader.java
@@ -14,7 +14,7 @@
* loader. They will be shared across test runs.
* <p>
* The list of excluded package paths is specified in
- * a properties file "excluded.properties" that is located in
+ * a properties file "excluded.properties" that is located in
* the same place as the TestCaseClassLoader class.
* <p>
* <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
@@ -22,204 +22,204 @@
* {@hide} - Not needed for 1.0 SDK
*/
public class TestCaseClassLoader extends ClassLoader {
- /** scanned class path */
- private Vector fPathItems;
- /** default excluded paths */
- private String[] defaultExclusions= {
- "junit.framework.",
- "junit.extensions.",
- "junit.runner."
- };
- /** name of excluded properties file */
- static final String EXCLUDED_FILE= "excluded.properties";
- /** excluded paths */
- private Vector fExcluded;
-
- /**
- * Constructs a TestCaseLoader. It scans the class path
- * and the excluded package paths
- */
- public TestCaseClassLoader() {
- this(System.getProperty("java.class.path"));
- }
-
- /**
- * Constructs a TestCaseLoader. It scans the class path
- * and the excluded package paths
- */
- public TestCaseClassLoader(String classPath) {
- scanPath(classPath);
- readExcludedPackages();
- }
+ /** scanned class path */
+ private Vector fPathItems;
+ /** default excluded paths */
+ private String[] defaultExclusions= {
+ "junit.framework.",
+ "junit.extensions.",
+ "junit.runner."
+ };
+ /** name of excluded properties file */
+ static final String EXCLUDED_FILE= "excluded.properties";
+ /** excluded paths */
+ private Vector fExcluded;
- private void scanPath(String classPath) {
- String separator= System.getProperty("path.separator");
- fPathItems= new Vector(10);
- StringTokenizer st= new StringTokenizer(classPath, separator);
- while (st.hasMoreTokens()) {
- fPathItems.addElement(st.nextToken());
- }
- }
-
- public URL getResource(String name) {
- return ClassLoader.getSystemResource(name);
- }
-
- public InputStream getResourceAsStream(String name) {
- return ClassLoader.getSystemResourceAsStream(name);
- }
-
- public boolean isExcluded(String name) {
- for (int i= 0; i < fExcluded.size(); i++) {
- if (name.startsWith((String) fExcluded.elementAt(i))) {
- return true;
- }
- }
- return false;
- }
-
- public synchronized Class loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
-
- Class c= findLoadedClass(name);
- if (c != null)
- return c;
- //
- // Delegate the loading of excluded classes to the
- // standard class loader.
- //
- if (isExcluded(name)) {
- try {
- c= findSystemClass(name);
- return c;
- } catch (ClassNotFoundException e) {
- // keep searching
- }
- }
- if (c == null) {
- byte[] data= lookupClassData(name);
- if (data == null)
- throw new ClassNotFoundException();
- c= defineClass(name, data, 0, data.length);
- }
- if (resolve)
- resolveClass(c);
- return c;
- }
-
- private byte[] lookupClassData(String className) throws ClassNotFoundException {
- byte[] data= null;
- for (int i= 0; i < fPathItems.size(); i++) {
- String path= (String) fPathItems.elementAt(i);
- String fileName= className.replace('.', '/')+".class";
- if (isJar(path)) {
- data= loadJarData(path, fileName);
- } else {
- data= loadFileData(path, fileName);
- }
- if (data != null)
- return data;
- }
- throw new ClassNotFoundException(className);
- }
-
- boolean isJar(String pathEntry) {
- return pathEntry.endsWith(".jar") ||
- pathEntry.endsWith(".apk") ||
- pathEntry.endsWith(".zip");
- }
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader() {
+ this(System.getProperty("java.class.path"));
+ }
- private byte[] loadFileData(String path, String fileName) {
- File file= new File(path, fileName);
- if (file.exists()) {
- return getClassData(file);
- }
- return null;
- }
-
- private byte[] getClassData(File f) {
- try {
- FileInputStream stream= new FileInputStream(f);
- ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
- byte[] b= new byte[1000];
- int n;
- while ((n= stream.read(b)) != -1)
- out.write(b, 0, n);
- stream.close();
- out.close();
- return out.toByteArray();
+ /**
+ * Constructs a TestCaseLoader. It scans the class path
+ * and the excluded package paths
+ */
+ public TestCaseClassLoader(String classPath) {
+ scanPath(classPath);
+ readExcludedPackages();
+ }
- } catch (IOException e) {
- }
- return null;
- }
+ private void scanPath(String classPath) {
+ String separator= System.getProperty("path.separator");
+ fPathItems= new Vector(10);
+ StringTokenizer st= new StringTokenizer(classPath, separator);
+ while (st.hasMoreTokens()) {
+ fPathItems.addElement(st.nextToken());
+ }
+ }
- private byte[] loadJarData(String path, String fileName) {
- ZipFile zipFile= null;
- InputStream stream= null;
- File archive= new File(path);
- if (!archive.exists())
- return null;
- try {
- zipFile= new ZipFile(archive);
- } catch(IOException io) {
- return null;
- }
- ZipEntry entry= zipFile.getEntry(fileName);
- if (entry == null)
- return null;
- int size= (int) entry.getSize();
- try {
- stream= zipFile.getInputStream(entry);
- byte[] data= new byte[size];
- int pos= 0;
- while (pos < size) {
- int n= stream.read(data, pos, data.length - pos);
- pos += n;
- }
- zipFile.close();
- return data;
- } catch (IOException e) {
- } finally {
- try {
- if (stream != null)
- stream.close();
- } catch (IOException e) {
- }
- }
- return null;
- }
-
- private void readExcludedPackages() {
- fExcluded= new Vector(10);
- for (int i= 0; i < defaultExclusions.length; i++)
- fExcluded.addElement(defaultExclusions[i]);
-
- InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
- if (is == null)
- return;
- Properties p= new Properties();
- try {
- p.load(is);
- }
- catch (IOException e) {
- return;
- } finally {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
- String key= (String)e.nextElement();
- if (key.startsWith("excluded.")) {
- String path= p.getProperty(key);
- path= path.trim();
- if (path.endsWith("*"))
- path= path.substring(0, path.length()-1);
- if (path.length() > 0)
- fExcluded.addElement(path);
- }
- }
- }
+ public URL getResource(String name) {
+ return ClassLoader.getSystemResource(name);
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ClassLoader.getSystemResourceAsStream(name);
+ }
+
+ public boolean isExcluded(String name) {
+ for (int i= 0; i < fExcluded.size(); i++) {
+ if (name.startsWith((String) fExcluded.elementAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public synchronized Class loadClass(String name, boolean resolve)
+ throws ClassNotFoundException {
+
+ Class c= findLoadedClass(name);
+ if (c != null)
+ return c;
+ //
+ // Delegate the loading of excluded classes to the
+ // standard class loader.
+ //
+ if (isExcluded(name)) {
+ try {
+ c= findSystemClass(name);
+ return c;
+ } catch (ClassNotFoundException e) {
+ // keep searching
+ }
+ }
+ if (c == null) {
+ byte[] data= lookupClassData(name);
+ if (data == null)
+ throw new ClassNotFoundException();
+ c= defineClass(name, data, 0, data.length);
+ }
+ if (resolve)
+ resolveClass(c);
+ return c;
+ }
+
+ private byte[] lookupClassData(String className) throws ClassNotFoundException {
+ byte[] data= null;
+ for (int i= 0; i < fPathItems.size(); i++) {
+ String path= (String) fPathItems.elementAt(i);
+ String fileName= className.replace('.', '/')+".class";
+ if (isJar(path)) {
+ data= loadJarData(path, fileName);
+ } else {
+ data= loadFileData(path, fileName);
+ }
+ if (data != null)
+ return data;
+ }
+ throw new ClassNotFoundException(className);
+ }
+
+ boolean isJar(String pathEntry) {
+ return pathEntry.endsWith(".jar") ||
+ pathEntry.endsWith(".apk") ||
+ pathEntry.endsWith(".zip");
+ }
+
+ private byte[] loadFileData(String path, String fileName) {
+ File file= new File(path, fileName);
+ if (file.exists()) {
+ return getClassData(file);
+ }
+ return null;
+ }
+
+ private byte[] getClassData(File f) {
+ try {
+ FileInputStream stream= new FileInputStream(f);
+ ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
+ byte[] b= new byte[1000];
+ int n;
+ while ((n= stream.read(b)) != -1)
+ out.write(b, 0, n);
+ stream.close();
+ out.close();
+ return out.toByteArray();
+
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ private byte[] loadJarData(String path, String fileName) {
+ ZipFile zipFile= null;
+ InputStream stream= null;
+ File archive= new File(path);
+ if (!archive.exists())
+ return null;
+ try {
+ zipFile= new ZipFile(archive);
+ } catch(IOException io) {
+ return null;
+ }
+ ZipEntry entry= zipFile.getEntry(fileName);
+ if (entry == null)
+ return null;
+ int size= (int) entry.getSize();
+ try {
+ stream= zipFile.getInputStream(entry);
+ byte[] data= new byte[size];
+ int pos= 0;
+ while (pos < size) {
+ int n= stream.read(data, pos, data.length - pos);
+ pos += n;
+ }
+ zipFile.close();
+ return data;
+ } catch (IOException e) {
+ } finally {
+ try {
+ if (stream != null)
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ return null;
+ }
+
+ private void readExcludedPackages() {
+ fExcluded= new Vector(10);
+ for (int i= 0; i < defaultExclusions.length; i++)
+ fExcluded.addElement(defaultExclusions[i]);
+
+ InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
+ if (is == null)
+ return;
+ Properties p= new Properties();
+ try {
+ p.load(is);
+ }
+ catch (IOException e) {
+ return;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
+ String key= (String)e.nextElement();
+ if (key.startsWith("excluded.")) {
+ String path= p.getProperty(key);
+ path= path.trim();
+ if (path.endsWith("*"))
+ path= path.substring(0, path.length()-1);
+ if (path.length() > 0)
+ fExcluded.addElement(path);
+ }
+ }
+ }
}
diff --git a/test-runner/src/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java
index 208dccd..3ac9d9e 100644
--- a/test-runner/src/junit/runner/TestCollector.java
+++ b/test-runner/src/junit/runner/TestCollector.java
@@ -5,13 +5,13 @@
/**
* Collects Test class names to be presented
- * by the TestSelector.
+ * by the TestSelector.
* @see TestSelector
* {@hide} - Not needed for 1.0 SDK
*/
public interface TestCollector {
- /**
- * Returns an enumeration of Strings with qualified class names
- */
- public Enumeration collectTests();
+ /**
+ * Returns an enumeration of Strings with qualified class names
+ */
+ public Enumeration collectTests();
}
diff --git a/test-runner/src/junit/runner/TestRunListener.java b/test-runner/src/junit/runner/TestRunListener.java
index 0e95819..0410f0c 100644
--- a/test-runner/src/junit/runner/TestRunListener.java
+++ b/test-runner/src/junit/runner/TestRunListener.java
@@ -6,15 +6,15 @@
* making it suitable for remote test execution.
* {@hide} - Not needed for 1.0 SDK
*/
- public interface TestRunListener {
- /* test status constants*/
- public static final int STATUS_ERROR= 1;
- public static final int STATUS_FAILURE= 2;
+public interface TestRunListener {
+ /* test status constants*/
+ public static final int STATUS_ERROR= 1;
+ public static final int STATUS_FAILURE= 2;
- public void testRunStarted(String testSuiteName, int testCount);
- public void testRunEnded(long elapsedTime);
- public void testRunStopped(long elapsedTime);
- public void testStarted(String testName);
- public void testEnded(String testName);
- public void testFailed(int status, String testName, String trace);
+ public void testRunStarted(String testSuiteName, int testCount);
+ public void testRunEnded(long elapsedTime);
+ public void testRunStopped(long elapsedTime);
+ public void testStarted(String testName);
+ public void testEnded(String testName);
+ public void testFailed(int status, String testName, String trace);
}
diff --git a/test-runner/src/junit/runner/TestSuiteLoader.java b/test-runner/src/junit/runner/TestSuiteLoader.java
index 39a4cf7..581ea23 100644
--- a/test-runner/src/junit/runner/TestSuiteLoader.java
+++ b/test-runner/src/junit/runner/TestSuiteLoader.java
@@ -4,6 +4,6 @@
* An interface to define how a test suite should be loaded.
*/
public interface TestSuiteLoader {
- abstract public Class load(String suiteClassName) throws ClassNotFoundException;
- abstract public Class reload(Class aClass) throws ClassNotFoundException;
+ abstract public Class load(String suiteClassName) throws ClassNotFoundException;
+ abstract public Class reload(Class aClass) throws ClassNotFoundException;
}
diff --git a/test-runner/src/junit/runner/Version.java b/test-runner/src/junit/runner/Version.java
index b4541ab..4a6dc85 100644
--- a/test-runner/src/junit/runner/Version.java
+++ b/test-runner/src/junit/runner/Version.java
@@ -4,11 +4,17 @@
* This class defines the current version of JUnit
*/
public class Version {
- private Version() {
- // don't instantiate
- }
+ private Version() {
+ // don't instantiate
+ }
- public static String id() {
- return "3.8.1";
- }
+ public static String id() {
+ return "4.10";
+ }
+
+ // android-changed
+ /** @hide - not needed for public API */
+ public static void main(String[] args) {
+ System.out.println(id());
+ }
}
diff --git a/test-runner/src/junit/textui/ResultPrinter.java b/test-runner/src/junit/textui/ResultPrinter.java
index 5c97112..4b26558 100644
--- a/test-runner/src/junit/textui/ResultPrinter.java
+++ b/test-runner/src/junit/textui/ResultPrinter.java
@@ -14,129 +14,129 @@
import junit.runner.BaseTestRunner;
public class ResultPrinter implements TestListener {
- PrintStream fWriter;
- int fColumn= 0;
-
- public ResultPrinter(PrintStream writer) {
- fWriter= writer;
- }
-
- /* API for use by textui.TestRunner
- */
+ PrintStream fWriter;
+ int fColumn= 0;
- synchronized void print(TestResult result, long runTime) {
- printHeader(runTime);
- printErrors(result);
- printFailures(result);
- printFooter(result);
- }
+ public ResultPrinter(PrintStream writer) {
+ fWriter= writer;
+ }
- void printWaitPrompt() {
- getWriter().println();
- getWriter().println("<RETURN> to continue");
- }
-
- /* Internal methods
- */
+ /* API for use by textui.TestRunner
+ */
- protected void printHeader(long runTime) {
- getWriter().println();
- getWriter().println("Time: "+elapsedTimeAsString(runTime));
- }
-
- protected void printErrors(TestResult result) {
- printDefects(result.errors(), result.errorCount(), "error");
- }
-
- protected void printFailures(TestResult result) {
- printDefects(result.failures(), result.failureCount(), "failure");
- }
-
- protected void printDefects(Enumeration booBoos, int count, String type) {
- if (count == 0) return;
- if (count == 1)
- getWriter().println("There was " + count + " " + type + ":");
- else
- getWriter().println("There were " + count + " " + type + "s:");
- for (int i= 1; booBoos.hasMoreElements(); i++) {
- printDefect((TestFailure) booBoos.nextElement(), i);
- }
- }
-
- public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
- printDefectHeader(booBoo, count);
- printDefectTrace(booBoo);
- }
+ synchronized void print(TestResult result, long runTime) {
+ printHeader(runTime);
+ printErrors(result);
+ printFailures(result);
+ printFooter(result);
+ }
- protected void printDefectHeader(TestFailure booBoo, int count) {
- // I feel like making this a println, then adding a line giving the throwable a chance to print something
- // before we get to the stack trace.
- getWriter().print(count + ") " + booBoo.failedTest());
- }
+ void printWaitPrompt() {
+ getWriter().println();
+ getWriter().println("<RETURN> to continue");
+ }
- protected void printDefectTrace(TestFailure booBoo) {
- getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
- }
+ /* Internal methods
+ */
- protected void printFooter(TestResult result) {
- if (result.wasSuccessful()) {
- getWriter().println();
- getWriter().print("OK");
- getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+ protected void printHeader(long runTime) {
+ getWriter().println();
+ getWriter().println("Time: "+elapsedTimeAsString(runTime));
+ }
- } else {
- getWriter().println();
- getWriter().println("FAILURES!!!");
- getWriter().println("Tests run: "+result.runCount()+
- ", Failures: "+result.failureCount()+
- ", Errors: "+result.errorCount());
- }
- getWriter().println();
- }
+ protected void printErrors(TestResult result) {
+ printDefects(result.errors(), result.errorCount(), "error");
+ }
+
+ protected void printFailures(TestResult result) {
+ printDefects(result.failures(), result.failureCount(), "failure");
+ }
+
+ protected void printDefects(Enumeration<TestFailure> booBoos, int count, String type) {
+ if (count == 0) return;
+ if (count == 1)
+ getWriter().println("There was " + count + " " + type + ":");
+ else
+ getWriter().println("There were " + count + " " + type + "s:");
+ for (int i= 1; booBoos.hasMoreElements(); i++) {
+ printDefect(booBoos.nextElement(), i);
+ }
+ }
+
+ public void printDefect(TestFailure booBoo, int count) { // only public for testing purposes
+ printDefectHeader(booBoo, count);
+ printDefectTrace(booBoo);
+ }
+
+ protected void printDefectHeader(TestFailure booBoo, int count) {
+ // I feel like making this a println, then adding a line giving the throwable a chance to print something
+ // before we get to the stack trace.
+ getWriter().print(count + ") " + booBoo.failedTest());
+ }
+
+ protected void printDefectTrace(TestFailure booBoo) {
+ getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace()));
+ }
+
+ protected void printFooter(TestResult result) {
+ if (result.wasSuccessful()) {
+ getWriter().println();
+ getWriter().print("OK");
+ getWriter().println (" (" + result.runCount() + " test" + (result.runCount() == 1 ? "": "s") + ")");
+
+ } else {
+ getWriter().println();
+ getWriter().println("FAILURES!!!");
+ getWriter().println("Tests run: "+result.runCount()+
+ ", Failures: "+result.failureCount()+
+ ", Errors: "+result.errorCount());
+ }
+ getWriter().println();
+ }
- /**
- * Returns the formatted string of the elapsed time.
- * Duplicated from BaseTestRunner. Fix it.
- */
- protected String elapsedTimeAsString(long runTime) {
- // The following line was altered for compatibility with
- // Android libraries.
- return Double.toString((double)runTime/1000);
- }
+ /**
+ * Returns the formatted string of the elapsed time.
+ * Duplicated from BaseTestRunner. Fix it.
+ */
+ protected String elapsedTimeAsString(long runTime) {
+ // The following line was altered for compatibility with
+ // Android libraries.
+ return Double.toString((double)runTime/1000);
+ }
- public PrintStream getWriter() {
- return fWriter;
- }
- /**
- * @see junit.framework.TestListener#addError(Test, Throwable)
- */
- public void addError(Test test, Throwable t) {
- getWriter().print("E");
- }
+ public PrintStream getWriter() {
+ return fWriter;
+ }
+ /**
+ * @see junit.framework.TestListener#addError(Test, Throwable)
+ */
+ public void addError(Test test, Throwable t) {
+ getWriter().print("E");
+ }
- /**
- * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
- */
- public void addFailure(Test test, AssertionFailedError t) {
- getWriter().print("F");
- }
+ /**
+ * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError)
+ */
+ public void addFailure(Test test, AssertionFailedError t) {
+ getWriter().print("F");
+ }
- /**
- * @see junit.framework.TestListener#endTest(Test)
- */
- public void endTest(Test test) {
- }
+ /**
+ * @see junit.framework.TestListener#endTest(Test)
+ */
+ public void endTest(Test test) {
+ }
- /**
- * @see junit.framework.TestListener#startTest(Test)
- */
- public void startTest(Test test) {
- getWriter().print(".");
- if (fColumn++ >= 40) {
- getWriter().println();
- fColumn= 0;
- }
- }
+ /**
+ * @see junit.framework.TestListener#startTest(Test)
+ */
+ public void startTest(Test test) {
+ getWriter().print(".");
+ if (fColumn++ >= 40) {
+ getWriter().println();
+ fColumn= 0;
+ }
+ }
}
diff --git a/test-runner/src/junit/textui/TestRunner.java b/test-runner/src/junit/textui/TestRunner.java
index 8bdc325..e955e0e 100644
--- a/test-runner/src/junit/textui/TestRunner.java
+++ b/test-runner/src/junit/textui/TestRunner.java
@@ -3,187 +3,201 @@
import java.io.PrintStream;
-import junit.framework.*;
-import junit.runner.*;
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+import junit.runner.BaseTestRunner;
+import junit.runner.Version;
/**
* A command line based tool to run tests.
* <pre>
* java junit.textui.TestRunner [-wait] TestCaseClass
* </pre>
- * TestRunner expects the name of a TestCase class as argument.
- * If this class defines a static <code>suite</code> method it
- * will be invoked and the returned test is run. Otherwise all
+ *
+ * <p>TestRunner expects the name of a TestCase class as argument.
+ * If this class defines a static <code>suite</code> method it
+ * will be invoked and the returned test is run. Otherwise all
* the methods starting with "test" having no arguments are run.
* <p>
* When the wait command line argument is given TestRunner
* waits until the users types RETURN.
* <p>
* TestRunner prints a trace as the tests are executed followed by a
- * summary at the end.
+ * summary at the end.
*/
public class TestRunner extends BaseTestRunner {
- private ResultPrinter fPrinter;
-
- public static final int SUCCESS_EXIT= 0;
- public static final int FAILURE_EXIT= 1;
- public static final int EXCEPTION_EXIT= 2;
+ private ResultPrinter fPrinter;
- /**
- * Constructs a TestRunner.
- */
- public TestRunner() {
- this(System.out);
- }
+ public static final int SUCCESS_EXIT= 0;
+ public static final int FAILURE_EXIT= 1;
+ public static final int EXCEPTION_EXIT= 2;
- /**
- * Constructs a TestRunner using the given stream for all the output
- */
- public TestRunner(PrintStream writer) {
- this(new ResultPrinter(writer));
- }
-
- /**
- * Constructs a TestRunner using the given ResultPrinter all the output
- */
- public TestRunner(ResultPrinter printer) {
- fPrinter= printer;
- }
-
- /**
- * Runs a suite extracted from a TestCase subclass.
- */
- static public void run(Class testClass) {
- run(new TestSuite(testClass));
- }
+ /**
+ * Constructs a TestRunner.
+ */
+ public TestRunner() {
+ this(System.out);
+ }
- /**
- * Runs a single test and collects its results.
- * This method can be used to start a test run
- * from your program.
- * <pre>
- * public static void main (String[] args) {
- * test.textui.TestRunner.run(suite());
- * }
- * </pre>
- */
- static public TestResult run(Test test) {
- TestRunner runner= new TestRunner();
- return runner.doRun(test);
- }
+ /**
+ * Constructs a TestRunner using the given stream for all the output
+ */
+ public TestRunner(PrintStream writer) {
+ this(new ResultPrinter(writer));
+ }
- /**
- * Runs a single test and waits until the user
- * types RETURN.
- */
- static public void runAndWait(Test suite) {
- TestRunner aTestRunner= new TestRunner();
- aTestRunner.doRun(suite, true);
- }
+ /**
+ * Constructs a TestRunner using the given ResultPrinter all the output
+ */
+ public TestRunner(ResultPrinter printer) {
+ fPrinter= printer;
+ }
- /**
- * Always use the StandardTestSuiteLoader. Overridden from
- * BaseTestRunner.
- */
- public TestSuiteLoader getLoader() {
- return new StandardTestSuiteLoader();
- }
+ /**
+ * Runs a suite extracted from a TestCase subclass.
+ */
+ static public void run(Class<? extends TestCase> testClass) {
+ run(new TestSuite(testClass));
+ }
- public void testFailed(int status, Test test, Throwable t) {
- }
-
- public void testStarted(String testName) {
- }
-
- public void testEnded(String testName) {
- }
+ /**
+ * Runs a single test and collects its results.
+ * This method can be used to start a test run
+ * from your program.
+ * <pre>
+ * public static void main (String[] args) {
+ * test.textui.TestRunner.run(suite());
+ * }
+ * </pre>
+ */
+ static public TestResult run(Test test) {
+ TestRunner runner= new TestRunner();
+ return runner.doRun(test);
+ }
- /**
- * Creates the TestResult to be used for the test run.
- */
- protected TestResult createTestResult() {
- return new TestResult();
- }
-
- public TestResult doRun(Test test) {
- return doRun(test, false);
- }
-
- public TestResult doRun(Test suite, boolean wait) {
- TestResult result= createTestResult();
- result.addListener(fPrinter);
- long startTime= System.currentTimeMillis();
- suite.run(result);
- long endTime= System.currentTimeMillis();
- long runTime= endTime-startTime;
- fPrinter.print(result, runTime);
+ /**
+ * Runs a single test and waits until the user
+ * types RETURN.
+ */
+ static public void runAndWait(Test suite) {
+ TestRunner aTestRunner= new TestRunner();
+ aTestRunner.doRun(suite, true);
+ }
- pause(wait);
- return result;
- }
+ @Override
+ public void testFailed(int status, Test test, Throwable t) {
+ }
- protected void pause(boolean wait) {
- if (!wait) return;
- fPrinter.printWaitPrompt();
- try {
- System.in.read();
- }
- catch(Exception e) {
- }
- }
-
- public static void main(String args[]) {
- TestRunner aTestRunner= new TestRunner();
- try {
- TestResult r= aTestRunner.start(args);
- if (!r.wasSuccessful())
- System.exit(FAILURE_EXIT);
- System.exit(SUCCESS_EXIT);
- } catch(Exception e) {
- System.err.println(e.getMessage());
- System.exit(EXCEPTION_EXIT);
- }
- }
+ @Override
+ public void testStarted(String testName) {
+ }
- /**
- * Starts a test run. Analyzes the command line arguments
- * and runs the given test suite.
- */
- protected TestResult start(String args[]) throws Exception {
- String testCase= "";
- boolean wait= false;
-
- for (int i= 0; i < args.length; i++) {
- if (args[i].equals("-wait"))
- wait= true;
- else if (args[i].equals("-c"))
- testCase= extractClassName(args[++i]);
- else if (args[i].equals("-v"))
- System.err.println("JUnit "+Version.id()+" by Kent Beck and Erich Gamma");
- else
- testCase= args[i];
- }
-
- if (testCase.equals(""))
- throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+ @Override
+ public void testEnded(String testName) {
+ }
- try {
- Test suite= getTest(testCase);
- return doRun(suite, wait);
- }
- catch(Exception e) {
- throw new Exception("Could not create and run test suite: "+e);
- }
- }
-
- protected void runFailed(String message) {
- System.err.println(message);
- System.exit(FAILURE_EXIT);
- }
-
- public void setPrinter(ResultPrinter printer) {
- fPrinter= printer;
- }
-
-
+ /**
+ * Creates the TestResult to be used for the test run.
+ */
+ protected TestResult createTestResult() {
+ return new TestResult();
+ }
+
+ public TestResult doRun(Test test) {
+ return doRun(test, false);
+ }
+
+ public TestResult doRun(Test suite, boolean wait) {
+ TestResult result= createTestResult();
+ result.addListener(fPrinter);
+ long startTime= System.currentTimeMillis();
+ suite.run(result);
+ long endTime= System.currentTimeMillis();
+ long runTime= endTime-startTime;
+ fPrinter.print(result, runTime);
+
+ pause(wait);
+ return result;
+ }
+
+ protected void pause(boolean wait) {
+ if (!wait) return;
+ fPrinter.printWaitPrompt();
+ try {
+ System.in.read();
+ }
+ catch(Exception e) {
+ }
+ }
+
+ public static void main(String args[]) {
+ TestRunner aTestRunner= new TestRunner();
+ try {
+ TestResult r= aTestRunner.start(args);
+ if (!r.wasSuccessful())
+ System.exit(FAILURE_EXIT);
+ System.exit(SUCCESS_EXIT);
+ } catch(Exception e) {
+ System.err.println(e.getMessage());
+ System.exit(EXCEPTION_EXIT);
+ }
+ }
+
+ /**
+ * Starts a test run. Analyzes the command line arguments
+ * and runs the given test suite.
+ */
+ public TestResult start(String args[]) throws Exception {
+ String testCase= "";
+ String method= "";
+ boolean wait= false;
+
+ for (int i= 0; i < args.length; i++) {
+ if (args[i].equals("-wait"))
+ wait= true;
+ else if (args[i].equals("-c"))
+ testCase= extractClassName(args[++i]);
+ else if (args[i].equals("-m")) {
+ String arg= args[++i];
+ int lastIndex= arg.lastIndexOf('.');
+ testCase= arg.substring(0, lastIndex);
+ method= arg.substring(lastIndex + 1);
+ } else if (args[i].equals("-v"))
+ System.err.println("JUnit " + Version.id() + " by Kent Beck and Erich Gamma");
+ else
+ testCase= args[i];
+ }
+
+ if (testCase.equals(""))
+ throw new Exception("Usage: TestRunner [-wait] testCaseName, where name is the name of the TestCase class");
+
+ try {
+ if (!method.equals(""))
+ return runSingleMethod(testCase, method, wait);
+ Test suite= getTest(testCase);
+ return doRun(suite, wait);
+ } catch (Exception e) {
+ throw new Exception("Could not create and run test suite: " + e);
+ }
+ }
+
+ protected TestResult runSingleMethod(String testCase, String method, boolean wait) throws Exception {
+ Class<? extends TestCase> testClass= loadSuiteClass(testCase).asSubclass(TestCase.class);
+ Test test= TestSuite.createTest(testClass, method);
+ return doRun(test, wait);
+ }
+
+ @Override
+ protected void runFailed(String message) {
+ System.err.println(message);
+ System.exit(FAILURE_EXIT);
+ }
+
+ public void setPrinter(ResultPrinter printer) {
+ fPrinter= printer;
+ }
+
+
}
diff --git a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
index d151d9e..77c0a3f 100644
--- a/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
+++ b/tests/DumpRenderTree/src/com/android/dumprendertree/TestShellActivity.java
@@ -168,7 +168,7 @@
}
// This is asynchronous, but it gets processed by WebCore before it starts loading pages.
- mWebViewClassic.useMockDeviceOrientation();
+ mWebViewClassic.setUseMockDeviceOrientation();
}
@Override
diff --git a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
index fc22472..f958ade 100644
--- a/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
+++ b/tests/DumpRenderTree2/src/com/android/dumprendertree2/LayoutTestsExecutor.java
@@ -394,7 +394,7 @@
webViewSettings.setPageCacheCapacity(0);
// This is asynchronous, but it gets processed by WebCore before it starts loading pages.
- WebViewClassic.fromWebView(mCurrentWebView).useMockDeviceOrientation();
+ WebViewClassic.fromWebView(mCurrentWebView).setUseMockDeviceOrientation();
// Must do this after setting the AppCache path.
WebStorage.getInstance().deleteAllData();
diff --git a/tests/GridLayoutTest/AndroidManifest.xml b/tests/GridLayoutTest/AndroidManifest.xml
index 141e8fa..677220d 100644
--- a/tests/GridLayoutTest/AndroidManifest.xml
+++ b/tests/GridLayoutTest/AndroidManifest.xml
@@ -83,6 +83,13 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+
+ <activity android:name="LayoutInsetsTest" android:label="LayoutInsetsTest">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
</application>
diff --git a/tests/GridLayoutTest/res/drawable/btn_default.xml b/tests/GridLayoutTest/res/drawable/btn_default.xml
new file mode 100644
index 0000000..c6cfda0
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/btn_default.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_window_focused="false" android:state_enabled="true"
+ android:drawable="@drawable/my_btn_default_normal" />
+ <item android:state_window_focused="false" android:state_enabled="false"
+ android:drawable="@drawable/my_btn_default_normal" />
+ <item android:state_pressed="true"
+ android:drawable="@drawable/my_btn_default_pressed" />
+ <item android:state_focused="true" android:state_enabled="true"
+ android:drawable="@drawable/my_btn_default_selected" />
+ <item android:state_enabled="true"
+ android:drawable="@drawable/my_btn_default_normal" />
+ <item android:state_focused="true"
+ android:drawable="@drawable/my_btn_default_normal_disable_focused" />
+ <item
+ android:drawable="@drawable/my_btn_default_normal_disable" />
+</selector>
diff --git a/tests/GridLayoutTest/res/drawable/my_btn_default_normal.9.png b/tests/GridLayoutTest/res/drawable/my_btn_default_normal.9.png
new file mode 100644
index 0000000..cd0b7d5
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/my_btn_default_normal.9.png
Binary files differ
diff --git a/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable.9.png b/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable.9.png
new file mode 100755
index 0000000..f4f01c7
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable.9.png
Binary files differ
diff --git a/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable_focused.9.png b/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable_focused.9.png
new file mode 100755
index 0000000..5376db2
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/my_btn_default_normal_disable_focused.9.png
Binary files differ
diff --git a/tests/GridLayoutTest/res/drawable/my_btn_default_pressed.9.png b/tests/GridLayoutTest/res/drawable/my_btn_default_pressed.9.png
new file mode 100755
index 0000000..4312c27
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/my_btn_default_pressed.9.png
Binary files differ
diff --git a/tests/GridLayoutTest/res/drawable/my_btn_default_selected.9.png b/tests/GridLayoutTest/res/drawable/my_btn_default_selected.9.png
new file mode 100755
index 0000000..06b7790
--- /dev/null
+++ b/tests/GridLayoutTest/res/drawable/my_btn_default_selected.9.png
Binary files differ
diff --git a/tests/GridLayoutTest/res/layout/grid7.xml b/tests/GridLayoutTest/res/layout/grid7.xml
index b9e58d7..0e5be0c 100644
--- a/tests/GridLayoutTest/res/layout/grid7.xml
+++ b/tests/GridLayoutTest/res/layout/grid7.xml
@@ -17,7 +17,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
- android:columnCount="2"
+
<Space
android:layout_row="0"
android:layout_column="0"
diff --git a/tests/GridLayoutTest/src/com/android/test/layout/LayoutInsetsTest.java b/tests/GridLayoutTest/src/com/android/test/layout/LayoutInsetsTest.java
new file mode 100644
index 0000000..74daccc
--- /dev/null
+++ b/tests/GridLayoutTest/src/com/android/test/layout/LayoutInsetsTest.java
@@ -0,0 +1,59 @@
+package com.android.test.layout;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.GridLayout;
+import android.widget.Space;
+import android.widget.TextView;
+
+import static android.text.InputType.TYPE_CLASS_TEXT;
+import static android.text.InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
+import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
+import static android.widget.GridLayout.*;
+import static android.widget.GridLayout.FILL;
+import static android.widget.GridLayout.spec;
+
+public class LayoutInsetsTest extends Activity {
+ public static View create(Context context) {
+ GridLayout p = new GridLayout(context);
+ p.setUseDefaultMargins(true);
+ p.setAlignmentMode(ALIGN_BOUNDS);
+ p.setOrientation(VERTICAL);
+
+ {
+ TextView c = new TextView(context);
+ c.setTextSize(32);
+ c.setText("Email setup");
+ p.addView(c);
+ }
+ {
+ Button c = new Button(context);
+ c.setBackgroundResource(R.drawable.btn_default);
+ c.setText("Manual setup");
+ p.addView(c);
+ c.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Button b = (Button) v;
+ b.setEnabled(false);
+ }
+ });
+ }
+
+ return p;
+ }
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ //getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+ getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.JELLY_BEAN;
+ setContentView(create(this));
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
index 3e2a2ca..f52fe6f 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_element.java
@@ -108,7 +108,7 @@
_RS_ASSERT("complexElemsimpleElem.getDataType() == NONE",
complexElem.getDataType() == DataType.NONE);
_RS_ASSERT("complexElem.getSizeBytes() == ScriptField_ComplexStruct.Item.sizeof",
- complexElem.getSizeBytes() == ScriptField_ComplexStruct.Item.sizeof);
+ complexElem.getBytesSize() == ScriptField_ComplexStruct.Item.sizeof);
for (int i = 0; i < subElemCount; i ++) {
_RS_ASSERT("complexElem.getSubElement(i) != null",
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
index 1de4d71..ca54ac4 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_raster.java
@@ -60,13 +60,13 @@
}
private void testJavaSide(RenderScript RS) {
- _RS_ASSERT("pointSpriteEnabled.getPointSpriteEnabled() == true",
- pointSpriteEnabled.getPointSpriteEnabled() == true);
+ _RS_ASSERT("pointSpriteEnabled.isPointSpriteEnabled() == true",
+ pointSpriteEnabled.isPointSpriteEnabled() == true);
_RS_ASSERT("pointSpriteEnabled.getCullMode() == ProgramRaster.CullMode.BACK",
pointSpriteEnabled.getCullMode() == ProgramRaster.CullMode.BACK);
- _RS_ASSERT("cullMode.getPointSpriteEnabled() == false",
- cullMode.getPointSpriteEnabled() == false);
+ _RS_ASSERT("cullMode.isPointSpriteEnabled() == false",
+ cullMode.isPointSpriteEnabled() == false);
_RS_ASSERT("cullMode.getCullMode() == ProgramRaster.CullMode.FRONT",
cullMode.getCullMode() == ProgramRaster.CullMode.FRONT);
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
index 72a401d..4410ee3 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_program_store.java
@@ -112,15 +112,15 @@
boolean B,
boolean A,
boolean dither) {
- _RS_ASSERT("ps.getDepthMaskEnabled() == depthMask", ps.getDepthMaskEnabled() == depthMask);
+ _RS_ASSERT("ps.isDepthMaskEnabled() == depthMask", ps.isDepthMaskEnabled() == depthMask);
_RS_ASSERT("ps.getDepthFunc() == df", ps.getDepthFunc() == df);
_RS_ASSERT("ps.getBlendSrcFunc() == bsf", ps.getBlendSrcFunc() == bsf);
_RS_ASSERT("ps.getBlendDstFunc() == bdf", ps.getBlendDstFunc() == bdf);
- _RS_ASSERT("ps.getColorMaskREnabled() == R", ps.getColorMaskREnabled() == R);
- _RS_ASSERT("ps.getColorMaskGEnabled() == G", ps.getColorMaskGEnabled() == G);
- _RS_ASSERT("ps.getColorMaskBEnabled() == B", ps.getColorMaskBEnabled() == B);
- _RS_ASSERT("ps.getColorMaskAEnabled() == A", ps.getColorMaskAEnabled() == A);
- _RS_ASSERT("ps.getDitherEnabled() == dither", ps.getDitherEnabled() == dither);
+ _RS_ASSERT("ps.isColorMaskRedEnabled() == R", ps.isColorMaskRedEnabled() == R);
+ _RS_ASSERT("ps.isColorMaskGreenEnabled() == G", ps.isColorMaskGreenEnabled() == G);
+ _RS_ASSERT("ps.isColorMaskBlueEnabled () == B", ps.isColorMaskBlueEnabled () == B);
+ _RS_ASSERT("ps.isColorMaskAlphaEnabled() == A", ps.isColorMaskAlphaEnabled() == A);
+ _RS_ASSERT("ps.isDitherEnabled() == dither", ps.isDitherEnabled() == dither);
}
void varyBuilderColorAndDither(ProgramStore.Builder pb,
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index ae01c75..5eac1f2 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -44,7 +44,7 @@
private final static String TAG = "NotificationTestList";
NotificationManager mNM;
- Vibrator mVibrator = new Vibrator();
+ Vibrator mVibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
Handler mHandler = new Handler();
long mActivityCreateTime = System.currentTimeMillis();
diff --git a/wifi/java/android/net/wifi/WifiMonitor.java b/wifi/java/android/net/wifi/WifiMonitor.java
index 03d5134..3bd03f5 100644
--- a/wifi/java/android/net/wifi/WifiMonitor.java
+++ b/wifi/java/android/net/wifi/WifiMonitor.java
@@ -21,6 +21,7 @@
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pProvDiscEvent;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.net.wifi.StateChangeResult;
import android.os.Message;
import android.util.Log;
@@ -29,6 +30,7 @@
import com.android.internal.util.Protocol;
import com.android.internal.util.StateMachine;
+import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
@@ -214,6 +216,52 @@
group_capab=0x0 */
private static final String P2P_PROV_DISC_SHOW_PIN_STR = "P2P-PROV-DISC-SHOW-PIN";
+ /*
+ * Protocol format is as follows.<br>
+ * See the Table.62 in the WiFi Direct specification for the detail.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300000101
+ * length=3, service type=0(ALL Service), transaction id=1,
+ * status=1(service protocol type not available)<br>
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 0300020201
+ * length=3, service type=2(UPnP), transaction id=2,
+ * status=1(service protocol type not available)
+ *
+ * P2P-SERV-DISC-RESP 42:fc:89:e1:e2:27 1 990002030010757569643a3131323
+ * 2646534652d383537342d353961622d393332322d3333333435363738393034343a3
+ * a75726e3a736368656d61732d75706e702d6f72673a736572766963653a436f6e746
+ * 56e744469726563746f72793a322c757569643a36383539646564652d383537342d3
+ * 53961622d393333322d3132333435363738393031323a3a75706e703a726f6f74646
+ * 576696365
+ * length=153,type=2(UPnP),transaction id=3,status=0
+ *
+ * UPnP Protocol format is as follows.
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ *
+ * version=0x10(UPnP1.0) data=usn:uuid:1122de4e-8574-59ab-9322-33345678
+ * 9044::urn:schemas-upnp-org:service:ContentDirectory:2,usn:uuid:6859d
+ * ede-8574-59ab-9332-123456789012::upnp:rootdevice
+ *
+ * P2P-SERV-DISC-RESP 58:17:0c:bc:dd:ca 21 1900010200045f6970
+ * 70c00c000c01094d795072696e746572c027
+ * length=25, type=1(Bonjour),transaction id=2,status=0
+ *
+ * Bonjour Protocol format is as follows.
+ * __________________________________________________________
+ * |DNS Name(Variable)|DNS Type(1)|Version(1)|RDATA(Variable)|
+ *
+ * DNS Name=_ipp._tcp.local.,DNS type=12(PTR), Version=1,
+ * RDATA=MyPrinter._ipp._tcp.local.
+ *
+ */
+ private static final String P2P_SERV_DISC_RESP_STR = "P2P-SERV-DISC-RESP";
+
private static final String HOST_AP_EVENT_PREFIX_STR = "AP";
/* AP-STA-CONNECTED 42:fc:89:a8:96:09 dev_addr=02:90:4c:a0:92:54 */
private static final String AP_STA_CONNECTED_STR = "AP-STA-CONNECTED";
@@ -268,6 +316,7 @@
public static final int P2P_PROV_DISC_ENTER_PIN_EVENT = BASE + 35;
public static final int P2P_PROV_DISC_SHOW_PIN_EVENT = BASE + 36;
public static final int P2P_FIND_STOPPED_EVENT = BASE + 37;
+ public static final int P2P_SERV_DISC_RESP_EVENT = BASE + 38;
/* hostap events */
public static final int AP_STA_DISCONNECTED_EVENT = BASE + 41;
@@ -558,6 +607,13 @@
} else if (dataString.startsWith(P2P_PROV_DISC_SHOW_PIN_STR)) {
mStateMachine.sendMessage(P2P_PROV_DISC_SHOW_PIN_EVENT,
new WifiP2pProvDiscEvent(dataString));
+ } else if (dataString.startsWith(P2P_SERV_DISC_RESP_STR)) {
+ List<WifiP2pServiceResponse> list = WifiP2pServiceResponse.newInstance(dataString);
+ if (list != null) {
+ mStateMachine.sendMessage(P2P_SERV_DISC_RESP_EVENT, list);
+ } else {
+ Log.e(TAG, "Null service resp " + dataString);
+ }
}
}
diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java
index db73ea8..4ec2e02 100644
--- a/wifi/java/android/net/wifi/WifiNative.java
+++ b/wifi/java/android/net/wifi/WifiNative.java
@@ -19,8 +19,9 @@
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pDevice;
-import android.os.SystemProperties;
import android.text.TextUtils;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
import android.util.Log;
import java.io.InputStream;
@@ -644,4 +645,78 @@
public String p2pPeer(String deviceAddress) {
return doStringCommand("P2P_PEER " + deviceAddress);
}
+
+ public boolean p2pServiceAdd(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_ADD bonjour <query hexdump> <RDATA hexdump>
+ * P2P_SERVICE_ADD upnp <version hex> <service>
+ *
+ * e.g)
+ * [Bonjour]
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * P2P_SERVICE_ADD bonjour 045f697070c00c000c01 094d795072696e746572c027
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * P2P_SERVICE_ADD bonjour 096d797072696e746572045f697070c00c001001
+ * 09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074
+ *
+ * [UPnP]
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1
+ * P2P_SERVICE_ADD upnp 10 uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_ADD";
+ command += (" " + s);
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceDel(WifiP2pServiceInfo servInfo) {
+ /*
+ * P2P_SERVICE_DEL bonjour <query hexdump>
+ * P2P_SERVICE_DEL upnp <version hex> <service>
+ */
+ for (String s : servInfo.getSupplicantQueryList()) {
+ String command = "P2P_SERVICE_DEL ";
+
+ String[] data = s.split(" ");
+ if (data.length < 2) {
+ return false;
+ }
+ if ("upnp".equals(data[0])) {
+ command += s;
+ } else if ("bonjour".equals(data[0])) {
+ command += data[0];
+ command += (" " + data[1]);
+ } else {
+ return false;
+ }
+ if (!doBooleanCommand(command)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean p2pServiceFlush() {
+ return doBooleanCommand("P2P_SERVICE_FLUSH");
+ }
+
+ public String p2pServDiscReq(String addr, String query) {
+ String command = "P2P_SERV_DISC_REQ";
+ command += (" " + addr);
+ command += (" " + query);
+
+ return doStringCommand(command);
+ }
+
+ public boolean p2pServDiscCancelReq(String id) {
+ return doBooleanCommand("P2P_SERV_DISC_CANCEL_REQ " + id);
+ }
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index 9ce2545..3751727 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
/**
* A class representing a Wi-Fi P2p device list
@@ -32,24 +33,28 @@
*/
public class WifiP2pDeviceList implements Parcelable {
- private Collection<WifiP2pDevice> mDevices;
+ private HashMap<String, WifiP2pDevice> mDevices;
public WifiP2pDeviceList() {
- mDevices = new ArrayList<WifiP2pDevice>();
+ mDevices = new HashMap<String, WifiP2pDevice>();
}
/** copy constructor */
public WifiP2pDeviceList(WifiP2pDeviceList source) {
if (source != null) {
- mDevices = source.getDeviceList();
+ for (WifiP2pDevice d : source.getDeviceList()) {
+ mDevices.put(d.deviceAddress, d);
+ }
}
}
/** @hide */
public WifiP2pDeviceList(ArrayList<WifiP2pDevice> devices) {
- mDevices = new ArrayList<WifiP2pDevice>();
+ mDevices = new HashMap<String, WifiP2pDevice>();
for (WifiP2pDevice device : devices) {
- mDevices.add(device);
+ if (device.deviceAddress != null) {
+ mDevices.put(device.deviceAddress, device);
+ }
}
}
@@ -62,37 +67,42 @@
/** @hide */
public void update(WifiP2pDevice device) {
- if (device == null) return;
- for (WifiP2pDevice d : mDevices) {
- //Found, update fields that can change
- if (d.equals(device)) {
- d.deviceName = device.deviceName;
- d.primaryDeviceType = device.primaryDeviceType;
- d.secondaryDeviceType = device.secondaryDeviceType;
- d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
- d.deviceCapability = device.deviceCapability;
- d.groupCapability = device.groupCapability;
- return;
- }
+ if (device == null || device.deviceAddress == null) return;
+ WifiP2pDevice d = mDevices.get(device.deviceAddress);
+ if (d != null) {
+ d.deviceName = device.deviceName;
+ d.primaryDeviceType = device.primaryDeviceType;
+ d.secondaryDeviceType = device.secondaryDeviceType;
+ d.wpsConfigMethodsSupported = device.wpsConfigMethodsSupported;
+ d.deviceCapability = device.deviceCapability;
+ d.groupCapability = device.groupCapability;
+ return;
}
//Not found, add a new one
- mDevices.add(device);
+ mDevices.put(device.deviceAddress, device);
+ }
+
+ /** @hide */
+ public WifiP2pDevice get(String deviceAddress) {
+ if (deviceAddress == null) return null;
+
+ return mDevices.get(deviceAddress);
}
/** @hide */
public boolean remove(WifiP2pDevice device) {
- if (device == null) return false;
- return mDevices.remove(device);
+ if (device == null || device.deviceAddress == null) return false;
+ return mDevices.remove(device.deviceAddress) != null;
}
/** Get the list of devices */
public Collection<WifiP2pDevice> getDeviceList() {
- return Collections.unmodifiableCollection(mDevices);
+ return Collections.unmodifiableCollection(mDevices.values());
}
public String toString() {
StringBuffer sbuf = new StringBuffer();
- for (WifiP2pDevice device : mDevices) {
+ for (WifiP2pDevice device : mDevices.values()) {
sbuf.append("\n").append(device);
}
return sbuf.toString();
@@ -106,7 +116,7 @@
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mDevices.size());
- for(WifiP2pDevice device : mDevices) {
+ for(WifiP2pDevice device : mDevices.values()) {
dest.writeParcelable(device, flags);
}
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index c7f6bf0..35f37a8 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -21,6 +21,14 @@
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pBonjourServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceResponse;
import android.os.Binder;
import android.os.IBinder;
import android.os.Handler;
@@ -36,6 +44,7 @@
import com.android.internal.util.Protocol;
import java.util.HashMap;
+import java.util.List;
/**
* This class provides the API for managing Wi-Fi peer-to-peer connectivity. This lets an
@@ -290,6 +299,61 @@
/** @hide */
public static final int RESPONSE_GROUP_INFO = BASE + 24;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE = BASE + 28;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_FAILED = BASE + 29;
+ /** @hide */
+ public static final int ADD_LOCAL_SERVICE_SUCCEEDED = BASE + 30;
+
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE = BASE + 31;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_FAILED = BASE + 32;
+ /** @hide */
+ public static final int REMOVE_LOCAL_SERVICE_SUCCEEDED = BASE + 33;
+
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES = BASE + 34;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_FAILED = BASE + 35;
+ /** @hide */
+ public static final int CLEAR_LOCAL_SERVICES_SUCCEEDED = BASE + 36;
+
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST = BASE + 37;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_FAILED = BASE + 38;
+ /** @hide */
+ public static final int ADD_SERVICE_REQUEST_SUCCEEDED = BASE + 39;
+
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST = BASE + 40;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_FAILED = BASE + 41;
+ /** @hide */
+ public static final int REMOVE_SERVICE_REQUEST_SUCCEEDED = BASE + 42;
+
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS = BASE + 43;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_FAILED = BASE + 44;
+ /** @hide */
+ public static final int CLEAR_SERVICE_REQUESTS_SUCCEEDED = BASE + 45;
+
+ /** @hide */
+ public static final int DISCOVER_SERVICES = BASE + 46;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_FAILED = BASE + 47;
+ /** @hide */
+ public static final int DISCOVER_SERVICES_SUCCEEDED = BASE + 48;
+
+ /** @hide */
+ public static final int PING = BASE + 49;
+
+ /** @hide */
+ public static final int RESPONSE_SERVICE = BASE + 50;
+
/**
* Create a new WifiP2pManager instance. Applications use
* {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve
@@ -321,6 +385,14 @@
*/
public static final int BUSY = 2;
+ /**
+ * Passed with {@link ActionListener#onFailure}.
+ * Indicates that the {@link #discoverServices} failed because no service
+ * requests are set.
+ * @hide
+ */
+ public static final int NO_SERVICE_REQUESTS = 3;
+
/** Interface for callback invocation when framework channel is lost */
public interface ChannelListener {
/**
@@ -370,6 +442,93 @@
}
/**
+ * Interface for callback invocation when service discovery response other than
+ * UPnP or Bonjour is received
+ * @hide
+ */
+ public interface ServiceResponseListener {
+
+ /**
+ * The requested service response is available.
+ *
+ * @param serviceType service type. see the service type of
+ * {@link WifiP2pServiceInfo}
+ * @param responseData service discovery response data based on the requested
+ * service protocol type. The format depends on the service type.
+ * @param srcDevice source device.
+ */
+ public void onServiceAvailable(int serviceType,
+ byte[] responseData, WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour service discovery response
+ * is received
+ * @hide
+ */
+ public interface BonjourServiceResponseListener {
+
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified Bonjour
+ * registration type returned the instance name.
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter".
+ * @param registrationType <br>
+ * e.g) "_ipp._tcp.local."
+ * @param srcDevice source device.
+ */
+ public void onBonjourServiceAvailable(String instanceName,
+ String registrationType, WifiP2pDevice srcDevice);
+
+ }
+
+ /**
+ * Interface for callback invocation when Bonjour TXT record is available
+ * for a service
+ * @hide
+ */
+ public interface BonjourTxtRecordListener {
+ /**
+ * The requested Bonjour service response is available.
+ *
+ * <p>This function is invoked when the device with the specified full
+ * service domain service returned TXT record.
+ *
+ * @param fullDomainName full domain name. <br>
+ * e.g) "MyPrinter._ipp._tcp.local.".
+ * @param record txt record.
+ * @param srcDevice source device.
+ */
+ public void onBonjourTxtRecordAvailable(String fullDomainName,
+ DnsSdTxtRecord record,
+ WifiP2pDevice srcDevice);
+ }
+
+ /**
+ * Interface for callback invocation when upnp service discovery response
+ * is received
+ * @hide
+ * */
+ public interface UpnpServiceResponseListener {
+
+ /**
+ * The requested upnp service response is available.
+ *
+ * <p>This function is invoked when the specified device or service is found.
+ *
+ * @param uniqueServiceNames The list of unique service names.<br>
+ * e.g) uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:1
+ * @param srcDevice source device.
+ */
+ public void onUpnpServiceAvailable(List<String> uniqueServiceNames,
+ WifiP2pDevice srcDevice);
+ }
+
+
+ /**
* A channel that connects the application to the Wifi p2p framework.
* Most p2p operations require a Channel as an argument. An instance of Channel is obtained
* by doing a call on {@link #initialize}
@@ -382,6 +541,10 @@
}
private final static int INVALID_LISTENER_KEY = 0;
private ChannelListener mChannelListener;
+ private ServiceResponseListener mServRspListener;
+ private BonjourServiceResponseListener mBonjourServRspListener;
+ private BonjourTxtRecordListener mBonjourTxtListener;
+ private UpnpServiceResponseListener mUpnpServRspListener;
private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>();
private Object mListenerMapLock = new Object();
private int mListenerKey = 0;
@@ -406,10 +569,17 @@
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_FAILED:
case WifiP2pManager.STOP_DISCOVERY_FAILED:
+ case WifiP2pManager.DISCOVER_SERVICES_FAILED:
case WifiP2pManager.CONNECT_FAILED:
case WifiP2pManager.CANCEL_CONNECT_FAILED:
case WifiP2pManager.CREATE_GROUP_FAILED:
case WifiP2pManager.REMOVE_GROUP_FAILED:
+ case WifiP2pManager.ADD_LOCAL_SERVICE_FAILED:
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED:
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED:
+ case WifiP2pManager.ADD_SERVICE_REQUEST_FAILED:
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED:
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED:
if (listener != null) {
((ActionListener) listener).onFailure(message.arg1);
}
@@ -417,10 +587,17 @@
/* ActionListeners grouped together */
case WifiP2pManager.DISCOVER_PEERS_SUCCEEDED:
case WifiP2pManager.STOP_DISCOVERY_SUCCEEDED:
+ case WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED:
case WifiP2pManager.CONNECT_SUCCEEDED:
case WifiP2pManager.CANCEL_CONNECT_SUCCEEDED:
case WifiP2pManager.CREATE_GROUP_SUCCEEDED:
case WifiP2pManager.REMOVE_GROUP_SUCCEEDED:
+ case WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED:
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED:
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED:
+ case WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED:
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED:
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED:
if (listener != null) {
((ActionListener) listener).onSuccess();
}
@@ -443,6 +620,10 @@
((GroupInfoListener) listener).onGroupInfoAvailable(group);
}
break;
+ case WifiP2pManager.RESPONSE_SERVICE:
+ WifiP2pServiceResponse resp = (WifiP2pServiceResponse) message.obj;
+ handleServiceResponse(resp);
+ break;
default:
Log.d(TAG, "Ignored " + message);
break;
@@ -450,7 +631,47 @@
}
}
- int putListener(Object listener) {
+ private void handleServiceResponse(WifiP2pServiceResponse resp) {
+ if (resp instanceof WifiP2pBonjourServiceResponse) {
+ handleBonjourServiceResponse((WifiP2pBonjourServiceResponse)resp);
+ } else if (resp instanceof WifiP2pUpnpServiceResponse) {
+ if (mUpnpServRspListener != null) {
+ handleUpnpServiceResponse((WifiP2pUpnpServiceResponse)resp);
+ }
+ } else {
+ if (mServRspListener != null) {
+ mServRspListener.onServiceAvailable(resp.getServiceType(),
+ resp.getRawData(), resp.getSrcDevice());
+ }
+ }
+ }
+
+ private void handleUpnpServiceResponse(WifiP2pUpnpServiceResponse resp) {
+ mUpnpServRspListener.onUpnpServiceAvailable(resp.getUniqueServiceNames(),
+ resp.getSrcDevice());
+ }
+
+ private void handleBonjourServiceResponse(WifiP2pBonjourServiceResponse resp) {
+ if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+ if (mBonjourServRspListener != null) {
+ mBonjourServRspListener.onBonjourServiceAvailable(
+ resp.getInstanceName(),
+ resp.getDnsQueryName(),
+ resp.getSrcDevice());
+ }
+ } else if (resp.getDnsType() == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ if (mBonjourTxtListener != null) {
+ mBonjourTxtListener.onBonjourTxtRecordAvailable(
+ resp.getDnsQueryName(),
+ resp.getTxtRecord(),
+ resp.getSrcDevice());
+ }
+ } else {
+ Log.e(TAG, "Unhandled resp " + resp);
+ }
+ }
+
+ private int putListener(Object listener) {
if (listener == null) return INVALID_LISTENER_KEY;
int key;
synchronized (mListenerMapLock) {
@@ -462,7 +683,7 @@
return key;
}
- Object getListener(int key) {
+ private Object getListener(int key) {
if (key == INVALID_LISTENER_KEY) return null;
synchronized (mListenerMapLock) {
return mListenerMap.remove(key);
@@ -470,6 +691,18 @@
}
}
+ private static void checkChannel(Channel c) {
+ if (c == null) throw new IllegalArgumentException("Channel needs to be initialized");
+ }
+
+ private static void checkServiceInfo(WifiP2pServiceInfo info) {
+ if (info == null) throw new IllegalArgumentException("service info is null");
+ }
+
+ private static void checkServiceRequest(WifiP2pServiceRequest req) {
+ if (req == null) throw new IllegalArgumentException("service request is null");
+ }
+
/**
* Registers the application with the Wi-Fi framework. This function
* must be the first to be called before any p2p operations are performed.
@@ -512,7 +745,7 @@
* @param listener for callbacks on success or failure. Can be null.
*/
public void discoverPeers(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(DISCOVER_PEERS, 0, c.putListener(listener));
}
@@ -522,7 +755,7 @@
* @hide
*/
public void stopPeerDiscovery(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(STOP_DISCOVERY, 0, c.putListener(listener));
}
@@ -549,7 +782,7 @@
* @param listener for callbacks on success or failure. Can be null.
*/
public void connect(Channel c, WifiP2pConfig config, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CONNECT, 0, c.putListener(listener), config);
}
@@ -565,7 +798,7 @@
* @param listener for callbacks on success or failure. Can be null.
*/
public void cancelConnect(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CANCEL_CONNECT, 0, c.putListener(listener));
}
@@ -589,7 +822,7 @@
* @param listener for callbacks on success or failure. Can be null.
*/
public void createGroup(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(CREATE_GROUP, 0, c.putListener(listener));
}
@@ -605,18 +838,225 @@
* @param listener for callbacks on success or failure. Can be null.
*/
public void removeGroup(Channel c, ActionListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REMOVE_GROUP, 0, c.putListener(listener));
}
/**
+ * Register a local service of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to add a local
+ * service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>The service information is set through the subclass of {@link WifiP2pServiceInfo}.<br>
+ * e.g ) {@link WifiP2pUpnpServiceInfo#newInstance} or
+ * {@link WifiP2pBonjourServiceInfo#newInstance}
+ *
+ * <p>If a local service is added, the framework responds the appropriate service discovery
+ * request automatically.
+ *
+ * <p>These service information will be clear when p2p is disabled or call
+ * {@link #removeLocalService} or {@link #clearLocalServices}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is a local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void addLocalService(Channel c, WifiP2pServiceInfo servInfo, ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(ADD_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Unregister a specified local service of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to remove a
+ * local service to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param servInfo is the local service information.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void removeLocalService(Channel c, WifiP2pServiceInfo servInfo,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceInfo(servInfo);
+ c.mAsyncChannel.sendMessage(REMOVE_LOCAL_SERVICE, 0, c.putListener(listener), servInfo);
+ }
+
+ /**
+ * Clear all registered local services of service discovery.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * local services to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void clearLocalServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_LOCAL_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Register a callback to be invoked on receiving service discovery response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ * @hide
+ */
+ public void setServiceResponseListener(Channel c,
+ ServiceResponseListener listener) {
+ checkChannel(c);
+ c.mServRspListener = listener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving Bonjour service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c
+ * @param servlistener is for listening to a Bonjour service response
+ * @param txtListener is for listening to a Bonjour TXT record
+ * @hide
+ */
+ public void setBonjourResponseListeners(Channel c,
+ BonjourServiceResponseListener servListener, BonjourTxtRecordListener txtListener) {
+ checkChannel(c);
+ c.mBonjourServRspListener = servListener;
+ c.mBonjourTxtListener = txtListener;
+ }
+
+ /**
+ * Register a callback to be invoked on receiving upnp service discovery
+ * response.
+ *
+ * <p> see {@link #discoverServices} for the detail.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on receiving service discovery response.
+ * @hide
+ */
+ public void setUpnpServiceResponseListener(Channel c,
+ UpnpServiceResponseListener listener) {
+ checkChannel(c);
+ c.mUpnpServRspListener = listener;
+ }
+
+ /**
+ * Initiate service discovery. A discovery process involves scanning for
+ * requested services for the purpose of establishing a connection to a peer
+ * that supports an available service.
+ *
+ * <p> The function call immediately returns after sending a request to start service
+ * discovery to the framework. The application is notified of a success or failure to initiate
+ * discovery through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p> The services to be discovered are specified with calls to {@link #addServiceRequest}.
+ *
+ * <p>The application is notified of the response against the service discovery request
+ * through listener callbacks registered by {@link #setServiceResponseListener} or
+ * {@link #setBonjourServiceResponseListener}, or {@link #setUpnpServiceResponseListener}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void discoverServices(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, 0, c.putListener(listener));
+ }
+
+ /**
+ * Add a service discovery request.
+ *
+ * <p> The function call immediately returns after sending a request to add service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * <p>After service discovery request is added, you can initiate service discovery by
+ * {@link #discoverServices}.
+ *
+ * <p>These information will be clear when wifi p2p is disabled or
+ * {@link #removeServiceRequest(Channel, WifiP2pServiceRequest, ActionListener)} or
+ * {@link #clearServiceRequests(Channel, ActionListener)} is called.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void addServiceRequest(Channel c,
+ WifiP2pServiceRequest req, ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(ADD_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Remove a specified service discovery request.
+ *
+ * <p> The function call immediately returns after sending a request to remove service
+ * discovery request to the framework. The application is notified of a success or failure to
+ * add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param req is the service discovery request.
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void removeServiceRequest(Channel c, WifiP2pServiceRequest req,
+ ActionListener listener) {
+ checkChannel(c);
+ checkServiceRequest(req);
+ c.mAsyncChannel.sendMessage(REMOVE_SERVICE_REQUEST, 0,
+ c.putListener(listener), req);
+ }
+
+ /**
+ * Clear all registered service discovery requests.
+ *
+ * <p> The function call immediately returns after sending a request to clear all
+ * service discovery requests to the framework. The application is notified of a success
+ * or failure to add service through listener callbacks {@link ActionListener#onSuccess} or
+ * {@link ActionListener#onFailure}.
+ *
+ * @param c is the channel created at {@link #initialize}
+ * @param listener for callbacks on success or failure. Can be null.
+ * @hide
+ */
+ public void clearServiceRequests(Channel c, ActionListener listener) {
+ checkChannel(c);
+ c.mAsyncChannel.sendMessage(CLEAR_SERVICE_REQUESTS,
+ 0, c.putListener(listener));
+ }
+
+ /**
* Request the current list of peers.
*
* @param c is the channel created at {@link #initialize}
* @param listener for callback when peer list is available. Can be null.
*/
public void requestPeers(Channel c, PeerListListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_PEERS, 0, c.putListener(listener));
}
@@ -627,7 +1067,7 @@
* @param listener for callback when connection info is available. Can be null.
*/
public void requestConnectionInfo(Channel c, ConnectionInfoListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_CONNECTION_INFO, 0, c.putListener(listener));
}
@@ -638,7 +1078,7 @@
* @param listener for callback when group info is available. Can be null.
*/
public void requestGroupInfo(Channel c, GroupInfoListener listener) {
- if (c == null) return;
+ checkChannel(c);
c.mAsyncChannel.sendMessage(REQUEST_GROUP_INFO, 0, c.putListener(listener));
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
index 32e1053..314e33e 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java
@@ -42,6 +42,9 @@
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
+import android.net.wifi.p2p.nsd.WifiP2pServiceRequest;
+import android.net.wifi.p2p.nsd.WifiP2pServiceResponse;
import android.os.Binder;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -55,6 +58,7 @@
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -71,7 +75,10 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
/**
* WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications
@@ -150,6 +157,16 @@
private NetworkInfo mNetworkInfo;
+ /* The transaction Id of service discovery request */
+ private byte mServiceTransactionId = 0;
+
+ /* Service discovery request ID of wpa_supplicant.
+ * null means it's not set yet. */
+ private String mServiceDiscReqId;
+
+ /* clients(application) information list. */
+ private HashMap<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
+
/* Is chosen as a unique range to avoid conflict with
the range defined in Tethering.java */
private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
@@ -313,6 +330,10 @@
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.BUSY);
@@ -329,6 +350,32 @@
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.BUSY);
break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.BUSY);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.BUSY);
+ break;
case WifiP2pManager.REQUEST_PEERS:
replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
break;
@@ -381,6 +428,10 @@
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
@@ -397,6 +448,32 @@
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ replyToMessage(message,
+ WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ replyToMessage(message,
+ WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED,
+ WifiP2pManager.P2P_UNSUPPORTED);
+ break;
default:
return NOT_HANDLED;
}
@@ -507,6 +584,8 @@
transitionTo(mP2pDisablingState);
break;
case WifiP2pManager.DISCOVER_PEERS:
+ // do not send service discovery request while normal find operation.
+ clearSupplicantServiceRequest();
if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
sendP2pDiscoveryChangedBroadcast(true);
@@ -526,6 +605,20 @@
WifiP2pManager.ERROR);
}
break;
+ case WifiP2pManager.DISCOVER_SERVICES:
+ if (DBG) logd(getName() + " discover services");
+ if (!updateSupplicantServiceRequest()) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.NO_SERVICE_REQUESTS);
+ break;
+ }
+ if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
+ WifiP2pManager.ERROR);
+ }
+ break;
case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
WifiP2pDevice device = (WifiP2pDevice) message.obj;
if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
@@ -536,7 +629,55 @@
device = (WifiP2pDevice) message.obj;
if (mPeers.remove(device)) sendP2pPeersChangedBroadcast();
break;
- default:
+ case WifiP2pManager.ADD_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " add service");
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj;
+ if (addLocalService(message.replyTo, servInfo)) {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED);
+ } else {
+ replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
+ }
+ break;
+ case WifiP2pManager.REMOVE_LOCAL_SERVICE:
+ if (DBG) logd(getName() + " remove service");
+ servInfo = (WifiP2pServiceInfo)message.obj;
+ removeLocalService(message.replyTo, servInfo);
+ replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_LOCAL_SERVICES:
+ if (DBG) logd(getName() + " clear service");
+ clearLocalServices(message.replyTo);
+ break;
+ case WifiP2pManager.ADD_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " add service request");
+ if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) {
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED);
+ break;
+ }
+ replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.REMOVE_SERVICE_REQUEST:
+ if (DBG) logd(getName() + " remove service request");
+ removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj);
+ replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED);
+ break;
+ case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
+ if (DBG) logd(getName() + " clear service request");
+ clearServiceRequests(message.replyTo);
+ replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
+ break;
+ case WifiMonitor.P2P_SERV_DISC_RESP_EVENT:
+ if (DBG) logd(getName() + " receive service response");
+ List<WifiP2pServiceResponse> sdRespList =
+ (List<WifiP2pServiceResponse>) message.obj;
+ for (WifiP2pServiceResponse resp : sdRespList) {
+ WifiP2pDevice dev =
+ mPeers.get(resp.getSrcDevice().deviceAddress);
+ resp.setSrcDevice(dev);
+ sendServiceResponse(resp);
+ }
+ break;
+ default:
return NOT_HANDLED;
}
return HANDLED;
@@ -1286,6 +1427,12 @@
mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
updateThisDevice(WifiP2pDevice.AVAILABLE);
if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress);
+
+ mClientInfoList.clear();
+ mWifiNative.p2pFlush();
+ mWifiNative.p2pServiceFlush();
+ mServiceTransactionId = 0;
+ mServiceDiscReqId = null;
}
private void updateThisDevice(int status) {
@@ -1342,5 +1489,262 @@
Slog.e(TAG, s);
}
+ /**
+ * Update service discovery request to wpa_supplicant.
+ */
+ private boolean updateSupplicantServiceRequest() {
+ clearSupplicantServiceRequest();
+
+ StringBuffer sb = new StringBuffer();
+ for (ClientInfo c: mClientInfoList.values()) {
+ int key;
+ WifiP2pServiceRequest req;
+ for (int i=0; i < c.mReqList.size(); i++) {
+ key = c.mReqList.keyAt(i);
+ req = c.mReqList.get(key);
+ if (req != null) {
+ sb.append(req.getSupplicantQuery());
+ }
+ }
+ }
+
+ if (sb.length() == 0) {
+ return false;
+ }
+
+ mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString());
+ if (mServiceDiscReqId == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Clear service discovery request in wpa_supplicant
+ */
+ private void clearSupplicantServiceRequest() {
+ if (mServiceDiscReqId == null) return;
+
+ mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId);
+ mServiceDiscReqId = null;
+ }
+
+ /* TODO: We could track individual service adds seperately and avoid
+ * having to do update all service requests on every new request
+ */
+ private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ req.setTransactionId(++mServiceTransactionId);
+ clientInfo.mReqList.put(mServiceTransactionId, req);
+
+ if (mServiceDiscReqId == null) {
+ return true;
+ }
+
+ return updateSupplicantServiceRequest();
+ }
+
+ private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ clientInfo.mReqList.remove(req.getTransactionId());
+
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private void clearServiceRequests(Messenger m) {
+
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ if (clientInfo.mReqList.size() == 0) {
+ return;
+ }
+
+ clientInfo.mReqList.clear();
+
+ if (clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove channel information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+
+ if (mServiceDiscReqId == null) {
+ return;
+ }
+
+ updateSupplicantServiceRequest();
+ }
+
+ private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ clearClientDeadChannels();
+ ClientInfo clientInfo = getClientInfo(m, true);
+ if (clientInfo == null) {
+ return false;
+ }
+
+ if (!clientInfo.mServList.add(servInfo)) {
+ return false;
+ }
+
+ if (!mWifiNative.p2pServiceAdd(servInfo)) {
+ clientInfo.mServList.remove(servInfo);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ mWifiNative.p2pServiceDel(servInfo);
+
+ clientInfo.mServList.remove(servInfo);
+ if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearLocalServices(Messenger m) {
+ ClientInfo clientInfo = getClientInfo(m, false);
+ if (clientInfo == null) {
+ return;
+ }
+
+ for (WifiP2pServiceInfo servInfo: clientInfo.mServList) {
+ mWifiNative.p2pServiceDel(servInfo);
+ }
+
+ clientInfo.mServList.clear();
+ if (clientInfo.mReqList.size() == 0) {
+ if (DBG) logd("remove client information from framework");
+ mClientInfoList.remove(clientInfo.mMessenger);
+ }
+ }
+
+ private void clearClientInfo(Messenger m) {
+ clearLocalServices(m);
+ clearServiceRequests(m);
+ }
+
+ /**
+ * Send the service response to the WifiP2pManager.Channel.
+ *
+ * @param resp
+ */
+ private void sendServiceResponse(WifiP2pServiceResponse resp) {
+ for (ClientInfo c : mClientInfoList.values()) {
+ WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId());
+ if (req != null) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.RESPONSE_SERVICE;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = resp;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ }
+ }
+ }
+ }
+
+ /**
+ * We dont get notifications of clients that have gone away.
+ * We detect this actively when services are added and throw
+ * them away.
+ *
+ * TODO: This can be done better with full async channels.
+ */
+ private void clearClientDeadChannels() {
+ for (ClientInfo c : mClientInfoList.values()) {
+ Message msg = Message.obtain();
+ msg.what = WifiP2pManager.PING;
+ msg.arg1 = 0;
+ msg.arg2 = 0;
+ msg.obj = null;
+ try {
+ c.mMessenger.send(msg);
+ } catch (RemoteException e) {
+ if (DBG) logd("detect dead channel");
+ clearClientInfo(c.mMessenger);
+ }
+ }
+ }
+
+ /**
+ * Return the specified ClientInfo.
+ * @param m Messenger
+ * @param createIfNotExist if true and the specified channel info does not exist,
+ * create new client info.
+ * @return the specified ClientInfo.
+ */
+ private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) {
+ ClientInfo clientInfo = mClientInfoList.get(m);
+
+ if (clientInfo == null && createIfNotExist) {
+ if (DBG) logd("add a new client");
+ clientInfo = new ClientInfo(m);
+ mClientInfoList.put(m, clientInfo);
+ }
+
+ return clientInfo;
+ }
+
+ }
+
+ /**
+ * Information about a particular client and we track the service discovery requests
+ * and the local services registered by the client.
+ */
+ private class ClientInfo {
+
+ /*
+ * A reference to WifiP2pManager.Channel handler.
+ * The response of this request is notified to WifiP2pManager.Channel handler
+ */
+ private Messenger mMessenger;
+
+ /*
+ * A service discovery request list.
+ */
+ private SparseArray<WifiP2pServiceRequest> mReqList;
+
+ /*
+ * A local service information list.
+ */
+ private List<WifiP2pServiceInfo> mServList;
+
+ private ClientInfo(Messenger m) {
+ mMessenger = m;
+ mReqList = new SparseArray();
+ mServList = new ArrayList<WifiP2pServiceInfo>();
+ }
}
}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
new file mode 100644
index 0000000..ed278d5
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceInfo.java
@@ -0,0 +1,223 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A class for Bonjour service information.
+ * @hide
+ */
+public class WifiP2pBonjourServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * Bonjour version 1.
+ * @hide
+ */
+ public static final int VERSION_1 = 0x01;
+
+ /**
+ * Pointer record.
+ * @hide
+ */
+ public static final int DNS_TYPE_PTR = 12;
+
+ /**
+ * Text record.
+ * @hide
+ */
+ public static final int DNS_TYPE_TXT = 16;
+
+ /**
+ * virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * Key: domain name Value: pointer address.<br>
+ */
+ private final static Map<String, String> sVmPacket;
+
+ static {
+ sVmPacket = new HashMap<String, String>();
+ sVmPacket.put("_tcp.local.", "c00c");
+ sVmPacket.put("local.", "c011");
+ sVmPacket.put("_udp.local.", "c01c");
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pBonjourServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create Bonjour service information object.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @param txtRecord text record.
+ * @return Bonjour service information object
+ */
+ public static WifiP2pBonjourServiceInfo newInstance(String instanceName,
+ String registrationType, DnsSdTxtRecord txtRecord) {
+ if (TextUtils.isEmpty(instanceName) || TextUtils.isEmpty(registrationType)) {
+ throw new IllegalArgumentException(
+ "instance name or registration type cannot be empty");
+ }
+
+ if (txtRecord == null) {
+ txtRecord = new DnsSdTxtRecord();
+ }
+
+ ArrayList<String> queries = new ArrayList<String>();
+ queries.add(createPtrServiceQuery(instanceName, registrationType));
+ queries.add(createTxtServiceQuery(instanceName, registrationType, txtRecord));
+
+ return new WifiP2pBonjourServiceInfo(queries);
+ }
+
+ /**
+ * Create wpa_supplicant service query for PTR record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @return wpa_supplicant service query.
+ */
+ private static String createPtrServiceQuery(String instanceName,
+ String registrationType) {
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+ sb.append(createRequest(registrationType, DNS_TYPE_PTR, VERSION_1));
+ sb.append(" ");
+
+ byte[] data = instanceName.getBytes();
+ sb.append(String.format("%02x", data.length));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(data));
+ // This is the start point of this response.
+ // Therefore, it indicates the request domain name.
+ sb.append("c027");
+ return sb.toString();
+ }
+
+ /**
+ * Create wpa_supplicant service query for TXT record.
+ *
+ * @param instanceName instance name.<br>
+ * e.g) "MyPrinter"
+ * @param registrationType registration type.<br>
+ * e.g) "_ipp._tcp.local."
+ * @param txtRecord TXT record.<br>
+ * @return wpa_supplicant service query.
+ */
+ public static String createTxtServiceQuery(String instanceName,
+ String registrationType,
+ DnsSdTxtRecord txtRecord) {
+
+
+ StringBuffer sb = new StringBuffer();
+ sb.append("bonjour ");
+
+ sb.append(createRequest((instanceName + "." + registrationType),
+ DNS_TYPE_TXT, VERSION_1));
+ sb.append(" ");
+ byte[] rawData = txtRecord.getRawData();
+ if (rawData.length == 0) {
+ sb.append("00");
+ } else {
+ sb.append(bin2HexStr(rawData));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Create bonjour service discovery request.
+ *
+ * @param dnsName dns name
+ * @param dnsType dns type
+ * @param version version number
+ * @hide
+ */
+ static String createRequest(String dnsName, int dnsType, int version) {
+ StringBuffer sb = new StringBuffer();
+
+ /*
+ * The request format is as follows.
+ * ________________________________________________
+ * | Encoded and Compressed dns name (variable) |
+ * ________________________________________________
+ * | Type (2) | Version (1) |
+ */
+ if (dnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ dnsName = dnsName.toLowerCase();
+ }
+ sb.append(compressDnsName(dnsName));
+ sb.append(String.format("%04x", dnsType));
+ sb.append(String.format("%02x", version));
+
+ return sb.toString();
+ }
+
+ /**
+ * Compress DNS data.
+ *
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.
+ *
+ * @param dnsName dns name
+ * @return compressed dns name
+ */
+ private static String compressDnsName(String dnsName) {
+ StringBuffer sb = new StringBuffer();
+
+ // The domain name is replaced with a pointer to a prior
+ // occurrence of the same name in virtual memory packet.
+ while (true) {
+ String data = sVmPacket.get(dnsName);
+ if (data != null) {
+ sb.append(data);
+ break;
+ }
+ int i = dnsName.indexOf('.');
+ if (i == -1) {
+ if (dnsName.length() > 0) {
+ sb.append(String.format("%02x", dnsName.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(dnsName.getBytes()));
+ }
+ // for a sequence of labels ending in a zero octet
+ sb.append("00");
+ break;
+ }
+
+ String name = dnsName.substring(0, i);
+ dnsName = dnsName.substring(i + 1);
+ sb.append(String.format("%02x", name.length()));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(name.getBytes()));
+ }
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
new file mode 100644
index 0000000..d1635f1
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceRequest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+
+/**
+ * A class for a request of bonjour service discovery.
+ * @hide
+ */
+public class WifiP2pBonjourServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ private WifiP2pBonjourServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ private WifiP2pBonjourServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, null);
+ }
+
+ private WifiP2pBonjourServiceRequest(String registrationType, int dnsType, int version) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR, WifiP2pBonjourServiceInfo.createRequest(
+ registrationType,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+ WifiP2pBonjourServiceInfo.VERSION_1));
+ }
+
+ /**
+ * Create a service discovery request to search all Bonjour services.
+ *
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance() {
+ return new WifiP2pBonjourServiceRequest();
+ }
+
+ /**
+ * Create a service discovery request to resolve the instance name with the specified
+ * registration type.
+ *
+ * @param registrationType registration type. Cannot be null <br>
+ * e.g) <br>
+ * "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp.local." (IP Printing over TCP)<br>
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance(String registrationType) {
+ if (registrationType == null) {
+ throw new IllegalArgumentException("registration type cannot be null");
+ }
+ return new WifiP2pBonjourServiceRequest(registrationType,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_PTR,
+ WifiP2pBonjourServiceInfo.VERSION_1);
+ }
+
+ /**
+ * Create a service discovery request to get the TXT data from the specified
+ * service.
+ *
+ * @param instanceName instance name. Cannot be null. <br>
+ * "MyPrinter"
+ * @param registrationType registration type. Cannot be null. <br>
+ * e.g) <br>
+ * "_afpovertcp._tcp.local."(Apple File Sharing over TCP)<br>
+ * "_ipp._tcp.local." (IP Printing over TCP)<br>
+ * @return service request for Bonjour.
+ */
+ public static WifiP2pBonjourServiceRequest newInstance(String instanceName,
+ String registrationType) {
+ if (instanceName == null || registrationType == null) {
+ throw new IllegalArgumentException(
+ "instance name or registration type cannot be null");
+ }
+ String fullDomainName = instanceName + "." + registrationType;
+ return new WifiP2pBonjourServiceRequest(fullDomainName,
+ WifiP2pBonjourServiceInfo.DNS_TYPE_TXT,
+ WifiP2pBonjourServiceInfo.VERSION_1);
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
new file mode 100644
index 0000000..c511569
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pBonjourServiceResponse.java
@@ -0,0 +1,313 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class for a response of bonjour service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pBonjourServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * DNS query name.
+ * e.g)
+ * for PTR
+ * "_ipp._tcp.local."
+ * for TXT
+ * "MyPrinter._ipp._tcp.local."
+ */
+ private String mDnsQueryName;
+
+ /**
+ * Service instance name.
+ * e.g) "MyPrinter"
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR}.
+ */
+ private String mInstanceName;
+
+ /**
+ * DNS Type.
+ * Should be {@link WifiP2pBonjourServiceInfo#DNS_TYPE_PTR} or
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+ */
+ private int mDnsType;
+
+ /**
+ * Bonjour version number.
+ * Should be {@link WifiP2pBonjourServiceInfo#VERSION_1}.
+ */
+ private int mVersion;
+
+ /**
+ * Txt record.
+ * This field is only used when the dns type equals to
+ * {@link WifiP2pBonjourServiceInfo#DNS_TYPE_TXT}.
+ */
+ private DnsSdTxtRecord mTxtRecord;
+
+ /**
+ * Virtual memory packet.
+ * see E.3 of the Wi-Fi Direct technical specification for the detail.<br>
+ * The spec can be obtained from wi-fi.org
+ * Key: pointer Value: domain name.<br>
+ */
+ private final static Map<Integer, String> sVmpack;
+
+ static {
+ sVmpack = new HashMap<Integer, String>();
+ sVmpack.put(0x0c, "_tcp.local.");
+ sVmpack.put(0x11, "local.");
+ sVmpack.put(0x1c, "_udp.local.");
+ }
+
+ /**
+ * Returns query DNS name.
+ * @return DNS name.
+ */
+ public String getDnsQueryName() {
+ return mDnsQueryName;
+ }
+
+ /**
+ * Return query DNS type.
+ * @return DNS type.
+ */
+ public int getDnsType() {
+ return mDnsType;
+ }
+
+ /**
+ * Return bonjour version number.
+ * @return version number.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return instance name.
+ * @return
+ */
+ public String getInstanceName() {
+ return mInstanceName;
+ }
+
+ /**
+ * Return TXT record data.
+ * @return TXT record data.
+ */
+ public DnsSdTxtRecord getTxtRecord() {
+ return mTxtRecord;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:Bonjour(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ sbuf.append(" dnsName:").append(mDnsQueryName);
+ if (mTxtRecord != null) {
+ sbuf.append(" TxtRecord:").append(mTxtRecord);
+ }
+ if (mInstanceName != null) {
+ sbuf.append(" InsName:").append(mInstanceName);
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * This is only used in framework.
+ * @param status status code.
+ * @param dev source device.
+ * @param data RDATA.
+ * @hide
+ */
+ protected WifiP2pBonjourServiceResponse(int status,
+ int tranId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR,
+ status, tranId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed bonjour service response");
+ }
+ }
+
+ /**
+ * Parse Bonjour service discovery response.
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format from Wi-Fi Direct spec is as follows.
+ * ________________________________________________
+ * | encoded and compressed dns name (variable) |
+ * ________________________________________________
+ * | dnstype(2byte) | version(1byte) |
+ * ________________________________________________
+ * | RDATA (variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(mData));
+
+ mDnsQueryName = readDnsName(dis);
+ if (mDnsQueryName == null) {
+ return false;
+ }
+
+ try {
+ mDnsType = dis.readUnsignedShort();
+ mVersion = dis.readUnsignedByte();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return false;
+ }
+
+ if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_PTR) {
+ String rData = readDnsName(dis);
+ if (rData == null) {
+ return false;
+ }
+ if (rData.length() <= mDnsQueryName.length()) {
+ return false;
+ }
+
+ mInstanceName = rData.substring(0,
+ rData.length() - mDnsQueryName.length() -1);
+ } else if (mDnsType == WifiP2pBonjourServiceInfo.DNS_TYPE_TXT) {
+ mTxtRecord = readTxtData(dis);
+ if (mTxtRecord == null) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read dns name.
+ *
+ * @param dis data input stream.
+ * @return dns name
+ */
+ private String readDnsName(DataInputStream dis) {
+ StringBuffer sb = new StringBuffer();
+
+ // copy virtual memory packet.
+ HashMap<Integer, String> vmpack = new HashMap<Integer, String>(sVmpack);
+ if (mDnsQueryName != null) {
+ vmpack.put(0x27, mDnsQueryName);
+ }
+ try {
+ while (true) {
+ int i = dis.readUnsignedByte();
+ if (i == 0x00) {
+ return sb.toString();
+ } else if (i == 0xc0) {
+ // refer to pointer.
+ String ref = vmpack.get(dis.readUnsignedByte());
+ if (ref == null) {
+ //invalid.
+ return null;
+ }
+ sb.append(ref);
+ return sb.toString();
+ } else {
+ byte[] data = new byte[i];
+ dis.readFully(data);
+ sb.append(new String(data));
+ sb.append(".");
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Read TXT record data.
+ *
+ * @param dis
+ * @return TXT record data
+ */
+ private DnsSdTxtRecord readTxtData(DataInputStream dis) {
+ DnsSdTxtRecord txtRecord = new DnsSdTxtRecord();
+ try {
+ while (dis.available() > 0) {
+ int len = dis.readUnsignedByte();
+ if (len == 0) {
+ break;
+ }
+ byte[] data = new byte[len];
+ dis.readFully(data);
+ String[] keyVal = new String(data).split("=");
+ if (keyVal.length != 2) {
+ return null;
+ }
+ txtRecord.set(keyVal[0], keyVal[1]);
+ }
+ return txtRecord;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ /**
+ * Creates Bonjour service response.
+ * This is only called from WifiP2pServiceResponse
+ *
+ * @param status status code.
+ * @param dev source device.
+ * @param data Bonjour response data.
+ * @return Bonjour service response data.
+ * @hide
+ */
+ static WifiP2pBonjourServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pBonjourServiceResponse(status,
+ transId, dev, null);
+ }
+ try {
+ return new WifiP2pBonjourServiceResponse(status,
+ transId, dev, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
new file mode 100644
index 0000000..cf2cb4a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceInfo;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
new file mode 100644
index 0000000..aed5616
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The class for service information.
+ *
+ * <p>Currently UPnP and Bonjour are only supported.
+ *
+ * @see WifiP2pUpnpServiceInfo
+ * @see WifiP2pBonjourServiceInfo
+ * @hide
+ */
+public class WifiP2pServiceInfo implements Parcelable {
+
+ /**
+ * All service protocol types.
+ */
+ public static final int SERVICE_TYPE_ALL = 0;
+
+ /**
+ * Bonjour protocol.
+ */
+ public static final int SERVICE_TYPE_BONJOUR = 1;
+
+ /**
+ * UPnP protocol.
+ */
+ public static final int SERVICE_TYPE_UPNP = 2;
+
+ /**
+ * WS-Discovery protocol
+ */
+ public static final int SERVICE_TYPE_WS_DISCOVERY = 3;
+
+ /**
+ * Vendor Specific protocol
+ */
+ public static final int SERVICE_TYPE_VENDOR_SPECIFIC = 255;
+
+ /**
+ * the list of query string for wpa_supplicant
+ *
+ * e.g)
+ * # IP Printing over TCP (PTR) (RDATA=MyPrinter._ipp._tcp.local.)
+ * {"bonjour", "045f697070c00c000c01", "094d795072696e746572c027"
+ *
+ * # IP Printing over TCP (TXT) (RDATA=txtvers=1,pdl=application/postscript)
+ * {"bonjour", "096d797072696e746572045f697070c00c001001",
+ * "09747874766572733d311a70646c3d6170706c69636174696f6e2f706f7374736372797074"}
+ *
+ * [UPnP]
+ * # UPnP uuid
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012"}
+ *
+ * # UPnP rootdevice
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ *
+ * # UPnP device
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp
+ * -org:device:InternetGatewayDevice:1"}
+ *
+ * # UPnP service
+ * {"upnp", "10", "uuid:6859dede-8574-59ab-9322-123456789012::urn:schemas-upnp
+ * -org:service:ContentDirectory:2"}
+ */
+ private List<String> mQueryList;
+
+ /**
+ * This is only used in subclass.
+ *
+ * @param queryList query string for wpa_supplicant
+ * @hide
+ */
+ protected WifiP2pServiceInfo(List<String> queryList) {
+ if (queryList == null) {
+ throw new IllegalArgumentException("query list cannot be null");
+ }
+ mQueryList = queryList;
+ }
+
+ /**
+ * Return the list of the query string for wpa_supplicant.
+ *
+ * @return the list of the query string for wpa_supplicant.
+ * @hide
+ */
+ public List<String> getSupplicantQueryList() {
+ return mQueryList;
+ }
+
+ /**
+ * Converts byte array to hex string.
+ *
+ * @param data
+ * @return hex string.
+ * @hide
+ */
+ static String bin2HexStr(byte[] data) {
+ StringBuffer sb = new StringBuffer();
+
+ for (byte b: data) {
+ String s = null;
+ try {
+ s = Integer.toHexString(b & 0xff);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ //add 0 padding
+ if (s.length() == 1) {
+ sb.append('0');
+ }
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceInfo)) {
+ return false;
+ }
+
+ WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)o;
+ return mQueryList.equals(servInfo.mQueryList);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mQueryList == null ? 0 : mQueryList.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeStringList(mQueryList);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceInfo> CREATOR =
+ new Creator<WifiP2pServiceInfo>() {
+ public WifiP2pServiceInfo createFromParcel(Parcel in) {
+
+ List<String> data = new ArrayList<String>();
+ in.readStringList(data);
+ return new WifiP2pServiceInfo(data);
+ }
+
+ public WifiP2pServiceInfo[] newArray(int size) {
+ return new WifiP2pServiceInfo[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
new file mode 100644
index 0000000..d5a1e8f
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceRequest;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
new file mode 100644
index 0000000..e41d9aa
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class for a request of service discovery.
+ *
+ * <p>This class is used when you create customized service discovery request.
+ * e.g) vendor specific request/ws discovery etc.
+ *
+ * <p>If you want to create UPnP or Bonjour service request, then you had better
+ * use {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}.
+ *
+ * @see WifiP2pUpnpServiceRequest
+ * @see WifiP2pBonjourServiceRequest
+ * @hide
+ */
+public class WifiP2pServiceRequest implements Parcelable {
+
+ /**
+ * Service type. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ private int mServiceType;
+
+ /**
+ * The length of the service request TLV.
+ * The value is equal to 2 plus the number of octets in the
+ * query data field.
+ */
+ private int mLength;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ private int mTransId;
+
+ /**
+ * The hex dump string of query data for the requested service information.
+ *
+ * e.g) Bonjour apple file sharing over tcp (dns name=_afpovertcp._tcp.local.)
+ * 0b5f6166706f766572746370c00c000c01
+ */
+ private String mQuery;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param serviceType service discovery type.
+ * @param query The part of service specific query.
+ * @hide
+ */
+ protected WifiP2pServiceRequest(int serviceType, String query) {
+ validateQuery(query);
+
+ mServiceType = serviceType;
+ mQuery = query;
+ if (query != null) {
+ mLength = query.length()/2 + 2;
+ } else {
+ mLength = 2;
+ }
+ }
+
+ /**
+ * This constructor is only used in Parcelable.
+ *
+ * @param serviceType service discovery type.
+ * @param length the length of service discovery packet.
+ * @param transId the transaction id
+ * @param query The part of service specific query.
+ */
+ private WifiP2pServiceRequest(int serviceType, int length,
+ int transId, String query) {
+ mServiceType = serviceType;
+ mLength = length;
+ mTransId = transId;
+ mQuery = query;
+ }
+
+ /**
+ * Return transaction id.
+ *
+ * @return transaction id
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Set transaction id.
+ *
+ * @param id
+ * @hide
+ */
+ public void setTransactionId(int id) {
+ mTransId = id;
+ }
+
+ /**
+ * Return wpa_supplicant request string.
+ *
+ * The format is the hex dump of the following frame.
+ * <pre>
+ * _______________________________________________________________
+ * | Length (2) | Type (1) | Transaction ID (1) |
+ * | Query Data (variable) |
+ * </pre>
+ *
+ * @return wpa_supplicant request string.
+ * @hide
+ */
+ public String getSupplicantQuery() {
+ StringBuffer sb = new StringBuffer();
+ // length is retained as little endian format.
+ sb.append(String.format("%02x", (mLength) & 0xff));
+ sb.append(String.format("%02x", (mLength >> 8) & 0xff));
+ sb.append(String.format("%02x", mServiceType));
+ sb.append(String.format("%02x", mTransId));
+ if (mQuery != null) {
+ sb.append(mQuery);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Validate query.
+ *
+ * <p>If invalid, throw IllegalArgumentException.
+ * @param query The part of service specific query.
+ */
+ private void validateQuery(String query) {
+ if (query == null) {
+ return;
+ }
+
+ int UNSIGNED_SHORT_MAX = 0xffff;
+ if (query.length()%2 == 1) {
+ throw new IllegalArgumentException(
+ "query size is invalid. query=" + query);
+ }
+ if (query.length()/2 > UNSIGNED_SHORT_MAX) {
+ throw new IllegalArgumentException(
+ "query size is too large. len=" + query.length());
+ }
+
+ // check whether query is hex string.
+ query = query.toLowerCase();
+ char[] chars = query.toCharArray();
+ for (char c: chars) {
+ if (!((c >= '0' && c <= '9') ||
+ (c >= 'a' && c <= 'f'))){
+ throw new IllegalArgumentException(
+ "query should be hex string. query=" + query);
+ }
+ }
+ }
+
+ /**
+ * Create service discovery request.
+ *
+ * <p>The created instance is set to framework by
+ * {@link WifiP2pManager#addLocalService}.
+ *
+ * @param serviceType service type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * If you want to use UPnP or Bonjour, you create the request by
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+ *
+ * @param query hex string. if null, all specified services are requested.
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int serviceType, String query) {
+ return new WifiP2pServiceRequest(serviceType, query);
+ }
+
+ /**
+ * Create all service discovery request.
+ *
+ * <p>The created instance is set to framework by
+ * {@link WifiP2pManager#addLocalService}.
+ *
+ * @param serviceType service type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_ALL},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_WS_DISCOVERY},
+ * {@link WifiP2pServiceInfo#SERVICE_TYPE_VENDOR_SPECIFIC}.
+ * If you want to use UPnP or Bonjour, you create the request by
+ * {@link WifiP2pUpnpServiceRequest} or {@link WifiP2pBonjourServiceRequest}
+ *
+ * @return service discovery request.
+ */
+ public static WifiP2pServiceRequest newInstance(int serviceType) {
+ return new WifiP2pServiceRequest(serviceType, null);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceRequest)) {
+ return false;
+ }
+
+ WifiP2pServiceRequest req = (WifiP2pServiceRequest)o;
+
+ /*
+ * Not compare transaction id.
+ * Transaction id may be changed on each service discovery operation.
+ */
+ if ((req.mServiceType != mServiceType) ||
+ (req.mLength != mLength)) {
+ return false;
+ }
+
+ if (req.mQuery == null && mQuery == null) {
+ return true;
+ } else if (req.mQuery != null) {
+ return req.mQuery.equals(mQuery);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mServiceType;
+ result = 31 * result + mLength;
+ result = 31 * result + (mQuery == null ? 0 : mQuery.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mServiceType);
+ dest.writeInt(mLength);
+ dest.writeInt(mTransId);
+ dest.writeString(mQuery);
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceRequest> CREATOR =
+ new Creator<WifiP2pServiceRequest>() {
+ public WifiP2pServiceRequest createFromParcel(Parcel in) {
+ int servType = in.readInt();
+ int length = in.readInt();
+ int transId = in.readInt();
+ String query = in.readString();
+ return new WifiP2pServiceRequest(servType, length, transId, query);
+ }
+
+ public WifiP2pServiceRequest[] newArray(int size) {
+ return new WifiP2pServiceRequest[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
new file mode 100644
index 0000000..c81d1f9
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.servicediscovery;
+
+parcelable WifiP2pServiceResponse;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
new file mode 100644
index 0000000..0855eae
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceResponse.java
@@ -0,0 +1,389 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The class for a response of service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pServiceResponse implements Parcelable {
+
+ private static int MAX_BUF_SIZE = 1024;
+
+ /**
+ * Service type. It's defined in table63 in Wi-Fi Direct specification.
+ */
+ protected int mServiceType;
+
+ /**
+ * Status code of service discovery response.
+ * It's defined in table65 in Wi-Fi Direct specification.
+ * @see Status
+ */
+ protected int mStatus;
+
+ /**
+ * Service transaction ID.
+ * This is a nonzero value used to match the service request/response TLVs.
+ */
+ protected int mTransId;
+
+ /**
+ * Source device.
+ */
+ protected WifiP2pDevice mDevice;
+
+ /**
+ * Service discovery response data based on the requested on
+ * the service protocol type. The protocol format depends on the service type.
+ */
+ protected byte[] mData;
+
+
+ /**
+ * The status code of service discovery response.
+ * Currently 4 status codes are defined and the status codes from 4 to 255
+ * are reserved.
+ *
+ * See Wi-Fi Direct specification for the detail.
+ */
+ public static class Status {
+ /** success */
+ public static final int SUCCESS = 0;
+
+ /** the service protocol type is not available */
+ public static final int SERVICE_PROTOCOL_NOT_AVAILABLE = 1;
+
+ /** the requested information is not available */
+ public static final int REQUESTED_INFORMATION_NOT_AVAILABLE = 2;
+
+ /** bad request */
+ public static final int BAD_REQUEST = 3;
+
+ /** @hide */
+ public static String toString(int status) {
+ switch(status) {
+ case SUCCESS:
+ return "SUCCESS";
+ case SERVICE_PROTOCOL_NOT_AVAILABLE:
+ return "SERVICE_PROTOCOL_NOT_AVAILABLE";
+ case REQUESTED_INFORMATION_NOT_AVAILABLE:
+ return "REQUESTED_INFORMATION_NOT_AVAILABLE";
+ case BAD_REQUEST:
+ return "BAD_REQUEST";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ /** not used */
+ private Status() {}
+ }
+
+ /**
+ * Hidden constructor. This is only used in framework.
+ *
+ * @param serviceType service discovery type.
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data query data.
+ */
+ protected WifiP2pServiceResponse(int serviceType, int status, int transId,
+ WifiP2pDevice device, byte[] data) {
+ mServiceType = serviceType;
+ mStatus = status;
+ mTransId = transId;
+ mDevice = device;
+ mData = data;
+ }
+
+ /**
+ * Return the service type of service discovery response.
+ *
+ * @return service discovery type.<br>
+ * e.g) {@link WifiP2pServiceInfo#SERVICE_TYPE_BONJOUR}
+ */
+ public int getServiceType() {
+ return mServiceType;
+ }
+
+ /**
+ * Return the status code of service discovery response.
+ *
+ * @return status code.
+ * @see Status
+ */
+ public int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Return the transaction id of service discovery response.
+ *
+ * @return transaction id.
+ * @hide
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Return response data.
+ *
+ * <pre>Data format depends on service type
+ *
+ * @return a query or response data.
+ */
+ public byte[] getRawData() {
+ return mData;
+ }
+
+ /**
+ * Returns the source device of service discovery response.
+ *
+ * <pre>This is valid only when service discovery response.
+ *
+ * @return the source device of service discovery response.
+ */
+ public WifiP2pDevice getSrcDevice() {
+ return mDevice;
+ }
+
+ /** @hide */
+ public void setSrcDevice(WifiP2pDevice dev) {
+ if (dev == null) return;
+ this.mDevice = dev;
+ }
+
+
+ /**
+ * Create the list of WifiP2pServiceResponse instance from supplicant event.
+ *
+ * <pre>The format is as follows.
+ * P2P-SERV-DISC-RESP <address> <update indicator> <response data>
+ * e.g) P2P-SERV-DISC-RESP 02:03:7f:11:62:da 1 0300000101
+ *
+ * @param supplicantEvent wpa_supplicant event string.
+ * @return if parse failed, return null
+ * @hide
+ */
+ public static List<WifiP2pServiceResponse> newInstance(String supplicantEvent) {
+
+ List<WifiP2pServiceResponse> respList = new ArrayList<WifiP2pServiceResponse>();
+ String[] args = supplicantEvent.split(" ");
+ if (args.length != 4) {
+ return null;
+ }
+ WifiP2pDevice dev = new WifiP2pDevice();
+ String srcAddr = args[1];
+ dev.deviceAddress = srcAddr;
+ //String updateIndicator = args[2];//not used.
+ byte[] bin = hexStr2Bin(args[3]);
+ if (bin == null) {
+ return null;
+ }
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bin));
+ try {
+ while (dis.available() > 0) {
+ /*
+ * Service discovery header is as follows.
+ * ______________________________________________________________
+ * | Length(2byte) | Type(1byte) | TransId(1byte)}|
+ * ______________________________________________________________
+ * | status(1byte) | vendor specific(variable) |
+ */
+ // The length equals to 3 plus the number of octets in the vendor
+ // specific content field. And this is little endian.
+ int length = ((dis.readByte() & 0xff) +
+ ((dis.readByte() & 0xff) << 8)) - 3;
+ int type = dis.readUnsignedByte();
+ byte transId = dis.readByte();
+ int status = dis.readUnsignedByte();
+ if (length < 0) {
+ return null;
+ }
+ if (length == 0) {
+ if (status == Status.SUCCESS) {
+ respList.add(new WifiP2pServiceResponse(type, status,
+ transId, dev, null));
+ }
+ continue;
+ }
+ if (length > MAX_BUF_SIZE) {
+ dis.skip(length);
+ continue;
+ }
+ byte[] data = new byte[length];
+ dis.readFully(data);
+
+ WifiP2pServiceResponse resp;
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ resp = WifiP2pBonjourServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ resp = WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else {
+ resp = new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+ if (resp != null && resp.getStatus() == Status.SUCCESS) {
+ respList.add(resp);
+ }
+ }
+ return respList;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (respList.size() > 0) {
+ return respList;
+ }
+ return null;
+ }
+
+ /**
+ * Converts hex string to byte array.
+ *
+ * @param hex hex string. if invalid, return null.
+ * @return binary data.
+ */
+ private static byte[] hexStr2Bin(String hex) {
+ int sz = hex.length()/2;
+ byte[] b = new byte[hex.length()/2];
+
+ for (int i=0;i<sz;i++) {
+ try {
+ b[i] = (byte)Integer.parseInt(hex.substring(i*2, i*2+2), 16);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return b;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:").append(mServiceType);
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" data:").append(mData);
+ return sbuf.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof WifiP2pServiceResponse)) {
+ return false;
+ }
+
+ WifiP2pServiceResponse req = (WifiP2pServiceResponse)o;
+
+ return (req.mServiceType == mServiceType) &&
+ (req.mStatus == mStatus) &&
+ equals(req.mDevice.deviceAddress, mDevice.deviceAddress) &&
+ Arrays.equals(req.mData, mData);
+ }
+
+ private boolean equals(Object a, Object b) {
+ if (a == null && b == null) {
+ return true;
+ } else if (a != null) {
+ return a.equals(b);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + mServiceType;
+ result = 31 * result + mStatus;
+ result = 31 * result + mTransId;
+ result = 31 * result + (mDevice.deviceAddress == null ?
+ 0 : mDevice.deviceAddress.hashCode());
+ result = 31 * result + (mData == null ? 0 : mData.hashCode());
+ return result;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mServiceType);
+ dest.writeInt(mStatus);
+ dest.writeInt(mTransId);
+ dest.writeParcelable(mDevice, flags);
+ if (mData == null || mData.length == 0) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(mData.length);
+ dest.writeByteArray(mData);
+ }
+ }
+
+ /** Implement the Parcelable interface {@hide} */
+ public static final Creator<WifiP2pServiceResponse> CREATOR =
+ new Creator<WifiP2pServiceResponse>() {
+ public WifiP2pServiceResponse createFromParcel(Parcel in) {
+
+ int type = in.readInt();
+ int status = in.readInt();
+ int transId = in.readInt();
+ WifiP2pDevice dev = (WifiP2pDevice)in.readParcelable(null);
+ int len = in.readInt();
+ byte[] data = null;
+ if (len > 0) {
+ data = new byte[len];
+ in.readByteArray(data);
+ }
+ if (type == WifiP2pServiceInfo.SERVICE_TYPE_BONJOUR) {
+ return WifiP2pBonjourServiceResponse.newInstance(status,
+ transId, dev, data);
+ } else if (type == WifiP2pServiceInfo.SERVICE_TYPE_UPNP) {
+ return WifiP2pUpnpServiceResponse.newInstance(status,
+ transId, dev, data);
+ }
+ return new WifiP2pServiceResponse(type, status, transId, dev, data);
+ }
+
+ public WifiP2pServiceResponse[] newArray(int size) {
+ return new WifiP2pServiceResponse[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
new file mode 100644
index 0000000..4d40e81
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceInfo.java
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * The class for UPnP service information.
+ * @hide
+ */
+public class WifiP2pUpnpServiceInfo extends WifiP2pServiceInfo {
+
+ /**
+ * UPnP version 1.0.
+ *
+ * <pre>Query Version should always be set to 0x10 if the query values are
+ * compatible with UPnP Device Architecture 1.0.
+ * @hide
+ */
+ public static final int VERSION_1_0 = 0x10;
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param queryList
+ */
+ private WifiP2pUpnpServiceInfo(List<String> queryList) {
+ super(queryList);
+ }
+
+ /**
+ * Create UPnP service information object.
+ *
+ * @param uuid a string representation of this UUID in the following format,
+ * as per <a href="http://www.ietf.org/rfc/rfc4122.txt">RFC 4122</a>.<br>
+ * e.g) 6859dede-8574-59ab-9332-123456789012
+ * @param device a string representation of this device in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:device:MediaServer:1
+ * @param services a string representation of this service in the following format,
+ * as per
+ * <a href="http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf">
+ * UPnP Device Architecture1.1</a><br>
+ * e.g) urn:schemas-upnp-org:service:ContentDirectory:1
+ * @return UPnP service information object.
+ */
+ public static WifiP2pUpnpServiceInfo newInstance(String uuid,
+ String device, List<String> services) {
+ if (uuid == null || device == null) {
+ throw new IllegalArgumentException("uuid or device cannnot be null");
+ }
+ UUID.fromString(uuid);
+
+ ArrayList<String> info = new ArrayList<String>();
+
+ info.add(createSupplicantQuery(uuid, null));
+ info.add(createSupplicantQuery(uuid, "upnp:rootdevice"));
+ info.add(createSupplicantQuery(uuid, device));
+ if (services != null) {
+ for (String service:services) {
+ info.add(createSupplicantQuery(uuid, service));
+ }
+ }
+
+ return new WifiP2pUpnpServiceInfo(info);
+ }
+
+ /**
+ * Create wpa_supplicant service query for upnp.
+ *
+ * @param uuid
+ * @param data
+ * @return wpa_supplicant service query for upnp
+ */
+ private static String createSupplicantQuery(String uuid, String data) {
+ StringBuffer sb = new StringBuffer();
+ sb.append("upnp ");
+ sb.append(String.format("%02x ", VERSION_1_0));
+ sb.append("uuid:");
+ sb.append(uuid);
+ if (data != null) {
+ sb.append("::");
+ sb.append(data);
+ }
+ return sb.toString();
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
new file mode 100644
index 0000000..b97637a
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceRequest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+/**
+ * The class for a request of upnp service discovery.
+ * @hide
+ */
+public class WifiP2pUpnpServiceRequest extends WifiP2pServiceRequest {
+
+ /**
+ * This constructor is only used in newInstance().
+ *
+ * @param query The part of service specific query.
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest(String query) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, query);
+ }
+
+ /**
+ * This constructor is only used in newInstance().
+ * @hide
+ */
+ protected WifiP2pUpnpServiceRequest() {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP, null);
+ }
+
+ /**
+ * Create a service discovery request to search all UPnP services.
+ *
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance() {
+ return new WifiP2pUpnpServiceRequest();
+ }
+ /**
+ * Create a service discovery request to search specified UPnP services.
+ *
+ * @param st ssdp search target. Cannot be null.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"ssdp:all"
+ * <li>"upnp:rootdevice"
+ * <li>"urn:schemas-upnp-org:device:MediaServer:2"
+ * <li>"urn:schemas-upnp-org:service:ContentDirectory:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * </ul>
+ * @return service request for UPnP.
+ */
+ public static WifiP2pUpnpServiceRequest newInstance(String st) {
+ if (st == null) {
+ throw new IllegalArgumentException("search target cannot be null");
+ }
+ StringBuffer sb = new StringBuffer();
+ sb.append(String.format("%02x", WifiP2pUpnpServiceInfo.VERSION_1_0));
+ sb.append(WifiP2pServiceInfo.bin2HexStr(st.getBytes()));
+ return new WifiP2pUpnpServiceRequest(sb.toString());
+ }
+}
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
new file mode 100644
index 0000000..ab95af6f6
--- /dev/null
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pUpnpServiceResponse.java
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ * 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.net.wifi.p2p.nsd;
+
+import android.net.wifi.p2p.WifiP2pDevice;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for a response of upnp service discovery.
+ *
+ * @hide
+ */
+public class WifiP2pUpnpServiceResponse extends WifiP2pServiceResponse {
+
+ /**
+ * UPnP version. should be {@link WifiP2pUpnpServiceInfo#VERSION_1_0}
+ */
+ private int mVersion;
+
+ /**
+ * The list of Unique Service Name.
+ * e.g)
+ *{"uuid:6859dede-8574-59ab-9332-123456789012",
+ *"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"}
+ */
+ private List<String> mUniqueServiceNames;
+
+ /**
+ * Return UPnP version number.
+ *
+ * @return version number.
+ * @see WifiP2pUpnpServiceInfo#VERSION_1_0
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Return Unique Service Name strings.
+ *
+ * @return Unique Service Name.<br>
+ * e.g ) <br>
+ * <ul>
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::upnp:rootdevice"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:device:
+ * MediaServer:2"
+ * <li>"uuid:6859dede-8574-59ab-9332-123456789012::urn:schemas-upnp-org:service:
+ * ContentDirectory:2"
+ * </ul>
+ */
+ public List<String> getUniqueServiceNames() {
+ return mUniqueServiceNames;
+ }
+
+ /**
+ * hidden constructor.
+ *
+ * @param status status code
+ * @param transId transaction id
+ * @param dev source device
+ * @param data UPnP response data.
+ */
+ protected WifiP2pUpnpServiceResponse(int status,
+ int transId, WifiP2pDevice dev, byte[] data) {
+ super(WifiP2pServiceInfo.SERVICE_TYPE_UPNP,
+ status, transId, dev, data);
+ if (!parse()) {
+ throw new IllegalArgumentException("Malformed upnp service response");
+ }
+ }
+
+ /**
+ * Parse UPnP service discovery response
+ *
+ * @return {@code true} if the operation succeeded
+ */
+ private boolean parse() {
+ /*
+ * The data format is as follows.
+ *
+ * ______________________________________________________
+ * | Version (1) | USN (Variable) |
+ */
+ if (mData == null) {
+ // the empty is OK.
+ return true;
+ }
+
+ if (mData.length < 1) {
+ return false;
+ }
+
+ mVersion = mData[0] & 0xff;
+ String[] names = new String(mData, 1, mData.length-1).split(",");
+ mUniqueServiceNames = new ArrayList<String>();
+ for (String name : names) {
+ mUniqueServiceNames.add(name);
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sbuf = new StringBuffer();
+ sbuf.append("serviceType:UPnP(").append(mServiceType).append(")");
+ sbuf.append(" status:").append(Status.toString(mStatus));
+ sbuf.append(" srcAddr:").append(mDevice.deviceAddress);
+ sbuf.append(" version:").append(String.format("%02x", mVersion));
+ if (mUniqueServiceNames != null) {
+ for (String name : mUniqueServiceNames) {
+ sbuf.append(" usn:").append(name);
+ }
+ }
+ return sbuf.toString();
+ }
+
+ /**
+ * Create upnp service response.
+ *
+ * <pre>This is only used in{@link WifiP2pServiceResponse}
+ *
+ * @param status status code.
+ * @param transId transaction id.
+ * @param device source device.
+ * @param data UPnP response data.
+ * @return UPnP service response data.
+ * @hide
+ */
+ static WifiP2pUpnpServiceResponse newInstance(int status,
+ int transId, WifiP2pDevice device, byte[] data) {
+ if (status != WifiP2pServiceResponse.Status.SUCCESS) {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, null);
+ }
+
+ try {
+ return new WifiP2pUpnpServiceResponse(status, transId, device, data);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}