Merge "Implement APIs for Bluetooth Health profile."
diff --git a/Android.mk b/Android.mk
index f87aaff..5f3327c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -366,6 +366,7 @@
-since ./frameworks/base/api/10.xml 10 \
-since ./frameworks/base/api/11.xml 11 \
-since ./frameworks/base/api/12.xml 12 \
+ -since ./frameworks/base/api/13.xml 13 \
-werror -hide 113 \
-overview $(LOCAL_PATH)/core/java/overview.html
@@ -416,6 +417,8 @@
resources/samples/NotePad "Note Pad" \
-samplecode $(sample_dir)/SampleSyncAdapter \
resources/samples/SampleSyncAdapter "Sample Sync Adapter" \
+ -samplecode $(sample_dir)/RandomMusicPlayer \
+ resources/samples/RandomMusicPlayer "Random Music Player" \
-samplecode $(sample_dir)/RenderScript \
resources/samples/RenderScript "RenderScript" \
-samplecode $(sample_dir)/SearchableDictionary \
diff --git a/api/current.txt b/api/current.txt
index fc244d8..19557f5 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8364,6 +8364,7 @@
public class SurfaceTexture {
ctor public SurfaceTexture(int);
+ ctor public SurfaceTexture(int, boolean);
method public long getTimestamp();
method public void getTransformMatrix(float[]);
method public void setOnFrameAvailableListener(android.graphics.SurfaceTexture.OnFrameAvailableListener);
@@ -10433,6 +10434,16 @@
method public abstract void onScanCompleted(java.lang.String, android.net.Uri);
}
+ public class Metadata {
+ method public boolean getBoolean(int);
+ method public boolean has(int);
+ method public java.util.Set<java.lang.Integer> keySet();
+ field public static final int PAUSE_AVAILABLE = 1; // 0x1
+ field public static final int SEEK_AVAILABLE = 4; // 0x4
+ field public static final int SEEK_BACKWARD_AVAILABLE = 2; // 0x2
+ field public static final int SEEK_FORWARD_AVAILABLE = 3; // 0x3
+ }
+
public class Ringtone {
method public int getStreamType();
method public java.lang.String getTitle(android.content.Context);
@@ -16782,6 +16793,9 @@
method public void addF32(android.renderscript.Float3);
method public void addF32(android.renderscript.Float4);
method public void addF64(double);
+ method public void addF64(android.renderscript.Double2);
+ method public void addF64(android.renderscript.Double3);
+ method public void addF64(android.renderscript.Double4);
method public void addI16(short);
method public void addI16(android.renderscript.Short2);
method public void addI16(android.renderscript.Short3);
@@ -16791,6 +16805,9 @@
method public void addI32(android.renderscript.Int3);
method public void addI32(android.renderscript.Int4);
method public void addI64(long);
+ method public void addI64(android.renderscript.Long2);
+ method public void addI64(android.renderscript.Long3);
+ method public void addI64(android.renderscript.Long4);
method public void addI8(byte);
method public void addI8(android.renderscript.Byte2);
method public void addI8(android.renderscript.Byte3);
@@ -16808,6 +16825,9 @@
method public void addU32(android.renderscript.Long3);
method public void addU32(android.renderscript.Long4);
method public void addU64(long);
+ method public void addU64(android.renderscript.Long2);
+ method public void addU64(android.renderscript.Long3);
+ method public void addU64(android.renderscript.Long4);
method public void addU8(short);
method public void addU8(android.renderscript.Short2);
method public void addU8(android.renderscript.Short3);
@@ -21626,6 +21646,7 @@
method public android.view.TextureView.SurfaceTextureListener getSurfaceTextureListener();
method public boolean isAvailable();
method protected final void onDraw(android.graphics.Canvas);
+ method public void setOpaque(boolean);
method public void setSurfaceTextureListener(android.view.TextureView.SurfaceTextureListener);
}
@@ -21633,6 +21654,7 @@
method public abstract void onSurfaceTextureAvailable(android.graphics.SurfaceTexture, int, int);
method public abstract void onSurfaceTextureDestroyed(android.graphics.SurfaceTexture);
method public abstract void onSurfaceTextureSizeChanged(android.graphics.SurfaceTexture, int, int);
+ method public abstract void onSurfaceTextureUpdated(android.graphics.SurfaceTexture);
}
public class TouchDelegate {
@@ -21695,7 +21717,10 @@
method public void dispatchDisplayHint(int);
method public boolean dispatchDragEvent(android.view.DragEvent);
method protected void dispatchDraw(android.graphics.Canvas);
+ method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
+ method protected boolean dispatchGenericPointerEvent(android.view.MotionEvent);
+ method protected boolean dispatchHoverEvent(android.view.MotionEvent);
method public boolean dispatchKeyEvent(android.view.KeyEvent);
method public boolean dispatchKeyEventPreIme(android.view.KeyEvent);
method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
@@ -21883,6 +21908,7 @@
method public void onFinishTemporaryDetach();
method protected void onFocusChanged(boolean, int, android.graphics.Rect);
method public boolean onGenericMotionEvent(android.view.MotionEvent);
+ method public void onHoverChanged(boolean);
method public boolean onHoverEvent(android.view.MotionEvent);
method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
method public void onInitializeAccessibilityNodeInfo(android.view.accessibility.AccessibilityNodeInfo);
@@ -22159,6 +22185,10 @@
method public abstract boolean onGenericMotion(android.view.View, android.view.MotionEvent);
}
+ public static abstract interface View.OnHoverListener {
+ method public abstract boolean onHover(android.view.View, android.view.MotionEvent);
+ }
+
public static abstract interface View.OnKeyListener {
method public abstract boolean onKey(android.view.View, int, android.view.KeyEvent);
}
@@ -22329,6 +22359,7 @@
method protected void measureChildren(int, int);
method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect);
method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect);
+ method public boolean onInterceptHoverEvent(android.view.MotionEvent);
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
method protected boolean onRequestFocusInDescendants(int, android.graphics.Rect);
@@ -24858,9 +24889,6 @@
}
public static abstract class GridLayout.Alignment {
- ctor public GridLayout.Alignment();
- method public abstract int getAlignmentValue(android.view.View, int, int);
- method public int getSizeInCell(android.view.View, int, int, int);
}
public static class GridLayout.Group {
@@ -25272,10 +25300,16 @@
method public void dismiss();
method public android.view.Menu getMenu();
method public android.view.MenuInflater getMenuInflater();
+ method public void inflate(int);
+ method public void setOnDismissListener(android.widget.PopupMenu.OnDismissListener);
method public void setOnMenuItemClickListener(android.widget.PopupMenu.OnMenuItemClickListener);
method public void show();
}
+ public static abstract interface PopupMenu.OnDismissListener {
+ method public abstract void onDismiss(android.widget.PopupMenu);
+ }
+
public static abstract interface PopupMenu.OnMenuItemClickListener {
method public abstract boolean onMenuItemClick(android.view.MenuItem);
}
@@ -27025,9 +27059,7 @@
public final class FilePermission extends java.security.Permission implements java.io.Serializable {
ctor public FilePermission(java.lang.String, java.lang.String);
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
@@ -29786,8 +29818,8 @@
ctor public DatagramSocket(java.net.SocketAddress) throws java.net.SocketException;
method public void bind(java.net.SocketAddress) throws java.net.SocketException;
method public void close();
- method public void connect(java.net.InetAddress, int);
method public void connect(java.net.SocketAddress) throws java.net.SocketException;
+ method public void connect(java.net.InetAddress, int);
method public void disconnect();
method public boolean getBroadcast() throws java.net.SocketException;
method public java.nio.channels.DatagramChannel getChannel();
@@ -30275,9 +30307,7 @@
public final class SocketPermission extends java.security.Permission implements java.io.Serializable {
ctor public SocketPermission(java.lang.String, java.lang.String);
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
@@ -31284,9 +31314,7 @@
public final class AllPermission extends java.security.Permission {
ctor public AllPermission(java.lang.String, java.lang.String);
ctor public AllPermission();
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
@@ -31300,9 +31328,7 @@
public abstract class BasicPermission extends java.security.Permission implements java.io.Serializable {
ctor public BasicPermission(java.lang.String);
ctor public BasicPermission(java.lang.String, java.lang.String);
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
@@ -31665,10 +31691,8 @@
public abstract class Permission implements java.security.Guard java.io.Serializable {
ctor public Permission(java.lang.String);
method public void checkGuard(java.lang.Object) throws java.lang.SecurityException;
- method public abstract boolean equals(java.lang.Object);
method public abstract java.lang.String getActions();
method public final java.lang.String getName();
- method public abstract int hashCode();
method public abstract boolean implies(java.security.Permission);
method public java.security.PermissionCollection newPermissionCollection();
}
@@ -31925,13 +31949,11 @@
public final class UnresolvedPermission extends java.security.Permission implements java.io.Serializable {
ctor public UnresolvedPermission(java.lang.String, java.lang.String, java.lang.String, java.security.cert.Certificate[]);
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
method public java.lang.String getUnresolvedActions();
method public java.security.cert.Certificate[] getUnresolvedCerts();
method public java.lang.String getUnresolvedName();
method public java.lang.String getUnresolvedType();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
@@ -39447,11 +39469,9 @@
public final class PrivateCredentialPermission extends java.security.Permission {
ctor public PrivateCredentialPermission(java.lang.String, java.lang.String);
- method public boolean equals(java.lang.Object);
method public java.lang.String getActions();
method public java.lang.String getCredentialClass();
method public java.lang.String[][] getPrincipals();
- method public int hashCode();
method public boolean implies(java.security.Permission);
}
diff --git a/build/phone-hdpi-512-dalvik-heap.mk b/build/phone-hdpi-512-dalvik-heap.mk
index 788b686..16e0505 100644
--- a/build/phone-hdpi-512-dalvik-heap.mk
+++ b/build/phone-hdpi-512-dalvik-heap.mk
@@ -19,5 +19,5 @@
PRODUCT_PROPERTY_OVERRIDES += \
dalvik.vm.heapstartsize=5m \
- dalvik.vm.heapgrowthlimit=32m \
+ dalvik.vm.heapgrowthlimit=48m \
dalvik.vm.heapsize=128m
diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp
index b48be6ef..bbd1a1b 100644
--- a/cmds/keystore/keystore.cpp
+++ b/cmds/keystore/keystore.cpp
@@ -363,6 +363,7 @@
response = writeMasterKey(pw);
}
if (response == NO_ERROR) {
+ memcpy(mMasterKey, masterKeyBlob.getValue(), MASTER_KEY_SIZE_BYTES);
setupMasterKeys();
}
return response;
diff --git a/cmds/keystore/test-keystore b/cmds/keystore/test-keystore
new file mode 100755
index 0000000..82b276f
--- /dev/null
+++ b/cmds/keystore/test-keystore
@@ -0,0 +1,272 @@
+#!/bin/bash
+#
+# Copyright 2011, 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 -e
+
+prefix=$0
+log_file=$prefix.log
+baseline_file=$prefix.baseline
+
+function cleanup_output() {
+ rm -f $log_file
+ rm -f $baseline_file
+}
+
+function log() {
+ echo "$@"
+ append $log_file \# "$@"
+ append $baseline_file \# "$@"
+}
+
+function expect() {
+ append $baseline_file "$@"
+}
+
+function append() {
+ declare -r file=$1
+ shift
+ echo "$@" >> $file
+}
+
+function run() {
+ # strip out carriage returns from adb
+ # strip out date/time from ls -l
+ "$@" | tr --delete '\r' | sed -E 's/[0-9]{4}-[0-9]{2}-[0-9]{2} +[0-9]{1,2}:[0-9]{2} //' >> $log_file
+}
+
+function keystore() {
+ declare -r user=$1
+ shift
+ run adb shell su $user keystore_cli "$@"
+}
+
+function list_keystore_directory() {
+ run adb shell ls -al /data/misc/keystore
+}
+
+function compare() {
+ log "comparing $baseline_file and $log_file"
+ diff $baseline_file $log_file || (log $tag FAILED && exit 1)
+}
+
+function test_basic() {
+
+ #
+ # reset
+ #
+ log "reset keystore as system user"
+ keystore system r
+ expect "1 No error"
+ list_keystore_directory
+
+ #
+ # basic tests as system/root
+ #
+ log "root does not have permission to run test"
+ keystore root t
+ expect "6 Permission denied"
+
+ log "but system user does"
+ keystore system t
+ expect "3 Uninitialized"
+ list_keystore_directory
+
+ log "password is now bar"
+ keystore system p bar
+ expect "1 No error"
+ list_keystore_directory
+ expect "-rw------- keystore keystore 84 .masterkey"
+
+ log "no error implies initialized and unlocked"
+ keystore system t
+ expect "1 No error"
+
+ log "saw with no argument"
+ keystore system s
+ expect "5 Protocol error"
+
+ log "saw nothing"
+ keystore system s ""
+ expect "1 No error"
+
+ log "add key baz"
+ keystore system i baz quux
+ expect "1 No error"
+
+ log "1000 is uid of system"
+ list_keystore_directory
+ expect "-rw------- keystore keystore 84 .masterkey"
+ expect "-rw------- keystore keystore 52 1000_baz"
+
+ log "saw baz"
+ keystore system s ""
+ expect "1 No error"
+ expect "baz"
+
+ log "system does not have access to read any keys"
+ keystore system g baz
+ expect "6 Permission denied"
+
+ log "however, root can read system user keys (as can wifi or vpn users)"
+ keystore root g baz
+ expect "1 No error"
+ expect "quux"
+
+ #
+ # app user tests
+ #
+
+ # app_0 has uid 10000, as seen below
+ log "other uses cannot see the system keys"
+ keystore app_0 g baz
+ expect "7 Key not found"
+
+ log "app user cannot use reset, password, lock, unlock"
+ keystore app_0 r
+ expect "6 Permission denied"
+ keystore app_0 p
+ expect "6 Permission denied"
+ keystore app_0 l
+ expect "6 Permission denied"
+ keystore app_0 u
+ expect "6 Permission denied"
+
+ log "install app_0 key"
+ keystore app_0 i 0x deadbeef
+ expect 1 No error
+ list_keystore_directory
+ expect "-rw------- keystore keystore 84 .masterkey"
+ expect "-rw------- keystore keystore 52 10000_0x"
+ expect "-rw------- keystore keystore 52 1000_baz"
+
+ log "get with no argument"
+ keystore app_0 g
+ expect "5 Protocol error"
+
+ keystore app_0 g 0x
+ expect "1 No error"
+ expect "deadbeef"
+
+ keystore app_0 i fred barney
+ expect "1 No error"
+
+ keystore app_0 s ""
+ expect "1 No error"
+ expect "0x"
+ expect "fred"
+
+ log "note that saw returns the suffix of prefix matches"
+ keystore app_0 s fr # fred
+ expect "1 No error"
+ expect "ed" # fred
+
+ #
+ # lock tests
+ #
+ log "lock the store as system"
+ keystore system l
+ expect "1 No error"
+ keystore system t
+ expect "2 Locked"
+
+ log "saw works while locked"
+ keystore app_0 s ""
+ expect "1 No error"
+ expect "0x"
+ expect "fred"
+
+ log "...but cannot read keys..."
+ keystore app_0 g 0x
+ expect "2 Locked"
+
+ log "...but they can be deleted."
+ keystore app_0 e 0x
+ expect "1 No error"
+ keystore app_0 d 0x
+ expect "1 No error"
+ keystore app_0 e 0x
+ expect "7 Key not found"
+
+ #
+ # password
+ #
+ log "wrong password"
+ keystore system u foo
+ expect "13 Wrong password (4 tries left)"
+ log "right password"
+ keystore system u bar
+ expect "1 No error"
+
+ log "make the password foo"
+ keystore system p foo
+ expect "1 No error"
+
+ #
+ # final reset
+ #
+ log "reset wipes everything for all users"
+ keystore system r
+ expect "1 No error"
+ list_keystore_directory
+
+ keystore system t
+ expect "3 Uninitialized"
+
+}
+
+function test_4599735() {
+ # http://b/4599735
+ log "start regression test for b/4599735"
+ keystore system r
+ expect "1 No error"
+
+ keystore system p foo
+ expect "1 No error"
+
+ keystore system i baz quux
+ expect "1 No error"
+
+ keystore root g baz
+ expect "1 No error"
+ expect "quux"
+
+ keystore system l
+ expect "1 No error"
+
+ keystore system p foo
+ expect "1 No error"
+
+ log "after unlock, regression led to result of '8 Value corrupted'"
+ keystore root g baz
+ expect "1 No error"
+ expect "quux"
+
+ keystore system r
+ expect "1 No error"
+ log "end regression test for b/4599735"
+}
+
+function main() {
+ cleanup_output
+ log $tag START
+ test_basic
+ test_4599735
+ compare
+ log $tag PASSED
+ cleanup_output
+}
+
+main
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index ca77185..d7b1e71 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -388,13 +388,15 @@
sp<MediaSource> mSource;
StreamType mStreamType;
+ bool mSawFirstIDRFrame;
DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource);
};
DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source)
: mSource(source),
- mStreamType(OTHER) {
+ mStreamType(OTHER),
+ mSawFirstIDRFrame(false) {
const char *mime;
CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
@@ -410,6 +412,8 @@
}
status_t DetectSyncSource::start(MetaData *params) {
+ mSawFirstIDRFrame = false;
+
return mSource->start(params);
}
@@ -439,16 +443,30 @@
status_t DetectSyncSource::read(
MediaBuffer **buffer, const ReadOptions *options) {
- status_t err = mSource->read(buffer, options);
+ for (;;) {
+ status_t err = mSource->read(buffer, options);
- if (err != OK) {
- return err;
- }
+ if (err != OK) {
+ return err;
+ }
- if (mStreamType == AVC && isIDRFrame(*buffer)) {
- (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
- } else {
- (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
+ if (mStreamType == AVC) {
+ bool isIDR = isIDRFrame(*buffer);
+ (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, isIDR);
+ if (isIDR) {
+ mSawFirstIDRFrame = true;
+ }
+ } else {
+ (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
+ }
+
+ if (mStreamType != AVC || mSawFirstIDRFrame) {
+ break;
+ }
+
+ // Ignore everything up to the first IDR frame.
+ (*buffer)->release();
+ *buffer = NULL;
}
return OK;
@@ -945,6 +963,17 @@
fprintf(stderr, "could not create extractor.\n");
return -1;
}
+
+ sp<MetaData> meta = extractor->getMetaData();
+
+ if (meta != NULL) {
+ const char *mime;
+ CHECK(meta->findCString(kKeyMIMEType, &mime));
+
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
+ syncInfoPresent = false;
+ }
+ }
}
size_t numTracks = extractor->countTracks();
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index c0c4c17..7183267 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.Intent;
import android.Manifest;
-import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays;
@@ -136,17 +135,8 @@
if (result != null) {
response.onResult(result);
}
- } catch (NetworkErrorException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount", e);
- }
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
- } catch (UnsupportedOperationException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "addAccount", e);
- }
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "addAccount not supported");
+ } catch (Exception e) {
+ handleException(response, "addAccount", accountType, e);
}
}
@@ -167,17 +157,8 @@
if (result != null) {
response.onResult(result);
}
- } catch (NetworkErrorException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "confirmCredentials", e);
- }
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
- } catch (UnsupportedOperationException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "confirmCredentials", e);
- }
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "confirmCredentials not supported");
+ } catch (Exception e) {
+ handleException(response, "confirmCredentials", account.toString(), e);
}
}
@@ -197,21 +178,9 @@
Log.v(TAG, "getAuthTokenLabel: result "
+ AccountManager.sanitizeResult(result));
}
- if (result != null) {
- response.onResult(result);
- }
- } catch (IllegalArgumentException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthTokenLabel", e);
- }
- response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
- "unknown authTokenType");
- } catch (UnsupportedOperationException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthTokenLabel", e);
- }
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "getAuthTokenTypeLabel not supported");
+ response.onResult(result);
+ } catch (Exception e) {
+ handleException(response, "getAuthTokenLabel", authTokenType, e);
}
}
@@ -234,17 +203,9 @@
if (result != null) {
response.onResult(result);
}
- } catch (UnsupportedOperationException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthToken", e);
- }
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "getAuthToken not supported");
- } catch (NetworkErrorException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "getAuthToken", e);
- }
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } catch (Exception e) {
+ handleException(response, "getAuthToken",
+ account.toString() + "," + authTokenType, e);
}
}
@@ -267,17 +228,9 @@
if (result != null) {
response.onResult(result);
}
- } catch (NetworkErrorException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "updateCredentials", e);
- }
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
- } catch (UnsupportedOperationException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "updateCredentials", e);
- }
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "updateCredentials not supported");
+ } catch (Exception e) {
+ handleException(response, "updateCredentials",
+ account.toString() + "," + authTokenType, e);
}
}
@@ -290,9 +243,8 @@
if (result != null) {
response.onResult(result);
}
- } catch (UnsupportedOperationException e) {
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "editProperties not supported");
+ } catch (Exception e) {
+ handleException(response, "editProperties", accountType, e);
}
}
@@ -305,11 +257,8 @@
if (result != null) {
response.onResult(result);
}
- } catch (UnsupportedOperationException e) {
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "hasFeatures not supported");
- } catch (NetworkErrorException e) {
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } catch (Exception e) {
+ handleException(response, "hasFeatures", account.toString(), e);
}
}
@@ -322,15 +271,38 @@
if (result != null) {
response.onResult(result);
}
- } catch (UnsupportedOperationException e) {
- response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
- "getAccountRemovalAllowed not supported");
- } catch (NetworkErrorException e) {
- response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } catch (Exception e) {
+ handleException(response, "getAccountRemovalAllowed", account.toString(), e);
}
}
}
+ private void handleException(IAccountAuthenticatorResponse response, String method,
+ String data, Exception e) throws RemoteException {
+ if (e instanceof NetworkErrorException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage());
+ } else if (e instanceof UnsupportedOperationException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+ method + " not supported");
+ } else if (e instanceof IllegalArgumentException) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, method + "(" + data + ")", e);
+ }
+ response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS,
+ method + " not supported");
+ } else {
+ Log.w(TAG, method + "(" + data + ")", e);
+ response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+ method + " failed");
+ }
+ }
+
private void checkBinderPermission() {
final int uid = Binder.getCallingUid();
final String perm = Manifest.permission.ACCOUNT_MANAGER;
diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java
index 7a465c1..491fcfe 100644
--- a/core/java/android/app/AlertDialog.java
+++ b/core/java/android/app/AlertDialog.java
@@ -890,7 +890,10 @@
public AlertDialog create() {
final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
P.apply(dialog.mAlert);
- dialog.setCanceledOnTouchOutside(P.mCancelable);
+ dialog.setCancelable(P.mCancelable);
+ if (P.mCancelable) {
+ dialog.setCanceledOnTouchOutside(true);
+ }
dialog.setOnCancelListener(P.mOnCancelListener);
if (P.mOnKeyListener != null) {
dialog.setOnKeyListener(P.mOnKeyListener);
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 9cb57be..41eea2e 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -80,6 +80,7 @@
private View mSearchPlate;
private SearchView mSearchView;
private Drawable mWorkingSpinner;
+ private View mCloseSearch;
// interaction with searchable application
private SearchableInfo mSearchable;
@@ -167,11 +168,18 @@
SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar);
searchBar.setSearchDialog(this);
mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view);
- mSearchView.setSubmitButtonEnabled(true);
mSearchView.setOnCloseListener(mOnCloseListener);
mSearchView.setOnQueryTextListener(mOnQueryChangeListener);
mSearchView.setOnSuggestionListener(mOnSuggestionSelectionListener);
+ mCloseSearch = findViewById(com.android.internal.R.id.closeButton);
+ mCloseSearch.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+
// TODO: Move the badge logic to SearchView or move the badge to search_bar.xml
mBadgeLabel = (TextView) mSearchView.findViewById(com.android.internal.R.id.search_badge);
mSearchAutoComplete = (AutoCompleteTextView)
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index acf2f2f1..1c9285e 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -432,17 +432,17 @@
// compatible with large screens, so diddle it.
CompatibilityInfo.computeCompatibleScaling(inoutDm, inoutDm);
} else {
- inoutDm.widthPixels = inoutDm.unscaledWidthPixels;
- inoutDm.heightPixels = inoutDm.unscaledHeightPixels;
+ inoutDm.widthPixels = inoutDm.noncompatWidthPixels;
+ inoutDm.heightPixels = inoutDm.noncompatHeightPixels;
}
if (isScalingRequired()) {
float invertedRatio = applicationInvertedScale;
- inoutDm.density *= invertedRatio;
+ inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
inoutDm.densityDpi = (int)((inoutDm.density*DisplayMetrics.DENSITY_DEFAULT)+.5f);
- inoutDm.scaledDensity *= invertedRatio;
- inoutDm.xdpi *= invertedRatio;
- inoutDm.ydpi *= invertedRatio;
+ inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
+ inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
+ inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
}
@@ -471,8 +471,8 @@
* @return Returns the scaling factor for the window.
*/
public static float computeCompatibleScaling(DisplayMetrics dm, DisplayMetrics outDm) {
- final int width = dm.unscaledWidthPixels;
- final int height = dm.unscaledHeightPixels;
+ final int width = dm.noncompatWidthPixels;
+ final int height = dm.noncompatHeightPixels;
int shortSize, longSize;
if (width < height) {
shortSize = width;
@@ -532,7 +532,9 @@
sb.append(applicationDensity);
sb.append("dpi");
if (isScalingRequired()) {
- sb.append(" scaling");
+ sb.append(" ");
+ sb.append(applicationScale);
+ sb.append("x");
}
if (!supportsScreen()) {
sb.append(" resizing");
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index bd8b1a4..324c9fd 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1416,23 +1416,19 @@
}
if (metrics != null) {
mMetrics.setTo(metrics);
- // NOTE: We should re-arrange this code to create a Display
- // with the CompatibilityInfo that is used everywhere we deal
- // with the display in relation to this app, rather than
- // doing the conversion here. This impl should be okay because
- // we make sure to return a compatible display in the places
- // where there are public APIs to retrieve the display... but
- // it would be cleaner and more maintainble to just be
- // consistently dealing with a compatible display everywhere in
- // the framework.
- if (mCompatibilityInfo != null) {
- mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
- }
}
+ // NOTE: We should re-arrange this code to create a Display
+ // with the CompatibilityInfo that is used everywhere we deal
+ // with the display in relation to this app, rather than
+ // doing the conversion here. This impl should be okay because
+ // we make sure to return a compatible display in the places
+ // where there are public APIs to retrieve the display... but
+ // it would be cleaner and more maintainble to just be
+ // consistently dealing with a compatible display everywhere in
+ // the framework.
if (mCompatibilityInfo != null) {
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
}
- mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
int configChanges = 0xfffffff;
if (config != null) {
mTmpConfig.setTo(config);
@@ -1448,6 +1444,7 @@
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
}
+ mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
String locale = null;
if (mConfiguration.locale != null) {
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 2b9c082..9bab797 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -82,11 +82,8 @@
/* Clears default preferences and permissions for the package */
void clearDefaults(String packageName);
- /* Sets the current primary USB function. */
- void setPrimaryFunction(String functions);
-
- /* Sets the default primary USB function. */
- void setDefaultFunction(String functions);
+ /* Sets the current USB function. */
+ void setCurrentFunction(String function, boolean makeDefault);
/* Sets the file path for USB mass storage backing file. */
void setMassStorageBackingFile(String path);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index a828a23..67d200c 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -408,32 +408,18 @@
}
/**
- * Sets the primary USB function.
+ * Sets the current USB function.
*
* @param function name of the USB function
+ * @param makeDefault true if this should be set as the default
*
* {@hide}
*/
- public void setPrimaryFunction(String function) {
+ public void setCurrentFunction(String function, boolean makeDefault) {
try {
- mService.setPrimaryFunction(function);
+ mService.setCurrentFunction(function, makeDefault);
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException in setPrimaryFunction", e);
- }
- }
-
- /**
- * Sets the default primary USB function.
- *
- * @param function name of the USB function
- *
- * {@hide}
- */
- public void setDefaultFunction(String function) {
- try {
- mService.setDefaultFunction(function);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in setDefaultFunction", e);
+ Log.e(TAG, "RemoteException in setCurrentFunction", e);
}
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 2242e9e..ce6f697 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -164,6 +164,12 @@
public static final String EXTRA_ERRORED_TETHER = "erroredArray";
/**
+ * The absence of APN..
+ * @hide
+ */
+ public static final int TYPE_NONE = -1;
+
+ /**
* The Default Mobile data connection. When active, all data traffic
* will use this connection by default.
*/
diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java
index c49c019..445b2f7 100644
--- a/core/java/android/net/DhcpStateMachine.java
+++ b/core/java/android/net/DhcpStateMachine.java
@@ -117,13 +117,14 @@
PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
mDhcpRenewWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_TAG);
+ mDhcpRenewWakeLock.setReferenceCounted(false);
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//DHCP renew
if (DBG) Log.d(TAG, "Sending a DHCP renewal " + this);
- //acquire a 40s wakelock to finish DHCP renewal
+ //Lock released after 40s in worst case scenario
mDhcpRenewWakeLock.acquire(40000);
sendMessage(CMD_RENEW_DHCP);
}
@@ -166,6 +167,7 @@
switch (message.what) {
case CMD_RENEW_DHCP:
Log.e(TAG, "Error! Failed to handle a DHCP renewal on " + mInterfaceName);
+ mDhcpRenewWakeLock.release();
break;
case SM_QUIT_CMD:
mContext.unregisterReceiver(mBroadcastReceiver);
@@ -268,10 +270,12 @@
/* Notify controller before starting DHCP */
mController.sendMessage(CMD_PRE_DHCP_ACTION);
transitionTo(mWaitBeforeRenewalState);
+ //mDhcpRenewWakeLock is released in WaitBeforeRenewalState
} else {
if (!runDhcp(DhcpAction.RENEW)) {
transitionTo(mStoppedState);
}
+ mDhcpRenewWakeLock.release();
}
break;
case CMD_START_DHCP:
@@ -318,6 +322,10 @@
}
return retValue;
}
+ @Override
+ public void exit() {
+ mDhcpRenewWakeLock.release();
+ }
}
private boolean runDhcp(DhcpAction dhcpAction) {
diff --git a/core/java/android/net/http/HttpResponseCache.java b/core/java/android/net/http/HttpResponseCache.java
index b5d64e4..5f65dfa 100644
--- a/core/java/android/net/http/HttpResponseCache.java
+++ b/core/java/android/net/http/HttpResponseCache.java
@@ -70,12 +70,15 @@
* the hit rate, but it may also just waste filesystem space!
*
* <p>For some applications it may be preferable to create the cache in the
- * external storage directory. Although it often has more free space, external
- * storage is optional and—even if available—can disappear during
- * use. Retrieve the external cache directory using {@link Context#getExternalCacheDir()}. If this method
- * returns null, your application should fall back to either not caching or
- * caching on non-external storage. If the external storage is removed during
- * use, the cache hit rate will drop to zero and ongoing cache reads will fail.
+ * external storage directory. <strong>There are no access controls on the
+ * external storage directory so it should not be used for caches that could
+ * contain private data.</strong> Although it often has more free space,
+ * external storage is optional and—even if available—can disappear
+ * during use. Retrieve the external cache directory using {@link
+ * Context#getExternalCacheDir()}. If this method returns null, your application
+ * should fall back to either not caching or caching on non-external storage. If
+ * the external storage is removed during use, the cache hit rate will drop to
+ * zero and ongoing cache reads will fail.
*
* <p>Flushing the cache forces its data to the filesystem. This ensures that
* all responses written to the cache will be readable the next time the
@@ -214,7 +217,7 @@
*/
public void flush() {
try {
- delegate.getCache().flush(); // TODO: fix flush() to not throw?
+ delegate.getCache().flush();
} catch (IOException ignored) {
}
}
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl
index 3f8b856..0c2a2fd 100755
--- a/core/java/android/nfc/INfcAdapterExtras.aidl
+++ b/core/java/android/nfc/INfcAdapterExtras.aidl
@@ -16,7 +16,6 @@
package android.nfc;
-import android.nfc.ApduList;
import android.os.Bundle;
@@ -29,4 +28,5 @@
Bundle transceive(in byte[] data_in);
int getCardEmulationRoute();
void setCardEmulationRoute(int route);
+ void authenticate(in byte[] token);
}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 5e99463..1b09242 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -226,4 +226,6 @@
*/
int getInterfaceTxThrottle(String iface);
+ void setBandwidthControlEnabled(boolean enabled);
+
}
diff --git a/core/java/android/pim/EventRecurrence.java b/core/java/android/pim/EventRecurrence.java
index 56c4f7a..cde7dac 100644
--- a/core/java/android/pim/EventRecurrence.java
+++ b/core/java/android/pim/EventRecurrence.java
@@ -18,36 +18,18 @@
import android.text.TextUtils;
import android.text.format.Time;
+import android.util.Log;
+import android.util.TimeFormatException;
import java.util.Calendar;
+import java.util.HashMap;
-public class EventRecurrence
-{
- /**
- * Thrown when a recurrence string provided can not be parsed according
- * to RFC2445.
- */
- public static class InvalidFormatException extends RuntimeException
- {
- InvalidFormatException(String s) {
- super(s);
- }
- }
+/**
+ * Event recurrence utility functions.
+ */
+public class EventRecurrence {
+ private static String TAG = "EventRecur";
- public EventRecurrence()
- {
- wkst = MO;
- }
-
- /**
- * Parse an iCalendar/RFC2445 recur type according to Section 4.3.10.
- */
- public native void parse(String recur);
-
- public void setStartDate(Time date) {
- startDate = date;
- }
-
public static final int SECONDLY = 1;
public static final int MINUTELY = 2;
public static final int HOURLY = 3;
@@ -64,13 +46,15 @@
public static final int FR = 0x00200000;
public static final int SA = 0x00400000;
- public Time startDate;
- public int freq;
+ public Time startDate; // set by setStartDate(), not parse()
+
+ public int freq; // SECONDLY, MINUTELY, etc.
public String until;
public int count;
public int interval;
- public int wkst;
+ public int wkst; // SU, MO, TU, etc.
+ /* lists with zero entries may be null references */
public int[] bysecond;
public int bysecondCount;
public int[] byminute;
@@ -79,7 +63,7 @@
public int byhourCount;
public int[] byday;
public int[] bydayNum;
- public int bydayCount;
+ public int bydayCount;
public int[] bymonthday;
public int bymonthdayCount;
public int[] byyearday;
@@ -91,6 +75,134 @@
public int[] bysetpos;
public int bysetposCount;
+ /** maps a part string to a parser object */
+ private static HashMap<String,PartParser> sParsePartMap;
+ static {
+ sParsePartMap = new HashMap<String,PartParser>();
+ sParsePartMap.put("FREQ", new ParseFreq());
+ sParsePartMap.put("UNTIL", new ParseUntil());
+ sParsePartMap.put("COUNT", new ParseCount());
+ sParsePartMap.put("INTERVAL", new ParseInterval());
+ sParsePartMap.put("BYSECOND", new ParseBySecond());
+ sParsePartMap.put("BYMINUTE", new ParseByMinute());
+ sParsePartMap.put("BYHOUR", new ParseByHour());
+ sParsePartMap.put("BYDAY", new ParseByDay());
+ sParsePartMap.put("BYMONTHDAY", new ParseByMonthDay());
+ sParsePartMap.put("BYYEARDAY", new ParseByYearDay());
+ sParsePartMap.put("BYWEEKNO", new ParseByWeekNo());
+ sParsePartMap.put("BYMONTH", new ParseByMonth());
+ sParsePartMap.put("BYSETPOS", new ParseBySetPos());
+ sParsePartMap.put("WKST", new ParseWkst());
+ }
+
+ /* values for bit vector that keeps track of what we have already seen */
+ private static final int PARSED_FREQ = 1 << 0;
+ private static final int PARSED_UNTIL = 1 << 1;
+ private static final int PARSED_COUNT = 1 << 2;
+ private static final int PARSED_INTERVAL = 1 << 3;
+ private static final int PARSED_BYSECOND = 1 << 4;
+ private static final int PARSED_BYMINUTE = 1 << 5;
+ private static final int PARSED_BYHOUR = 1 << 6;
+ private static final int PARSED_BYDAY = 1 << 7;
+ private static final int PARSED_BYMONTHDAY = 1 << 8;
+ private static final int PARSED_BYYEARDAY = 1 << 9;
+ private static final int PARSED_BYWEEKNO = 1 << 10;
+ private static final int PARSED_BYMONTH = 1 << 11;
+ private static final int PARSED_BYSETPOS = 1 << 12;
+ private static final int PARSED_WKST = 1 << 13;
+
+ /** maps a FREQ value to an integer constant */
+ private static final HashMap<String,Integer> sParseFreqMap = new HashMap<String,Integer>();
+ static {
+ sParseFreqMap.put("SECONDLY", SECONDLY);
+ sParseFreqMap.put("MINUTELY", MINUTELY);
+ sParseFreqMap.put("HOURLY", HOURLY);
+ sParseFreqMap.put("DAILY", DAILY);
+ sParseFreqMap.put("WEEKLY", WEEKLY);
+ sParseFreqMap.put("MONTHLY", MONTHLY);
+ sParseFreqMap.put("YEARLY", YEARLY);
+ }
+
+ /** maps a two-character weekday string to an integer constant */
+ private static final HashMap<String,Integer> sParseWeekdayMap = new HashMap<String,Integer>();
+ static {
+ sParseWeekdayMap.put("SU", SU);
+ sParseWeekdayMap.put("MO", MO);
+ sParseWeekdayMap.put("TU", TU);
+ sParseWeekdayMap.put("WE", WE);
+ sParseWeekdayMap.put("TH", TH);
+ sParseWeekdayMap.put("FR", FR);
+ sParseWeekdayMap.put("SA", SA);
+ }
+
+ /** If set, allow lower-case recurrence rule strings. Minor performance impact. */
+ private static final boolean ALLOW_LOWER_CASE = false;
+
+ /** If set, validate the value of UNTIL parts. Minor performance impact. */
+ private static final boolean VALIDATE_UNTIL = false;
+
+ /** If set, require that only one of {UNTIL,COUNT} is present. Breaks compat w/ old parser. */
+ private static final boolean ONLY_ONE_UNTIL_COUNT = false;
+
+
+ /**
+ * Thrown when a recurrence string provided can not be parsed according
+ * to RFC2445.
+ */
+ public static class InvalidFormatException extends RuntimeException {
+ InvalidFormatException(String s) {
+ super(s);
+ }
+ }
+
+ /**
+ * Parse an iCalendar/RFC2445 recur type according to Section 4.3.10. The string is
+ * parsed twice, by the old and new parsers, and the results are compared.
+ * <p>
+ * TODO: this will go away, and what is now parse2() will simply become parse().
+ */
+ public void parse(String recur) {
+ InvalidFormatException newExcep = null;
+ try {
+ parse2(recur);
+ } catch (InvalidFormatException ife) {
+ newExcep = ife;
+ }
+
+ boolean oldThrew = false;
+ try {
+ EventRecurrence check = new EventRecurrence();
+ check.parseNative(recur);
+ if (newExcep == null) {
+ // Neither threw, check to see if results match.
+ if (!equals(check)) {
+ throw new InvalidFormatException("Recurrence rule parse does not match [" +
+ recur + "]");
+ }
+ }
+ } catch (InvalidFormatException ife) {
+ oldThrew = true;
+ if (newExcep == null) {
+ // Old threw, but new didn't. Log a warning, but don't throw.
+ Log.d(TAG, "NOTE: old parser rejected [" + recur + "]: " + ife.getMessage());
+ }
+ }
+
+ if (newExcep != null) {
+ if (!oldThrew) {
+ // New threw, but old didn't. Log a warning and throw the exception.
+ Log.d(TAG, "NOTE: new parser rejected [" + recur + "]: " + newExcep.getMessage());
+ }
+ throw newExcep;
+ }
+ }
+
+ native void parseNative(String recur);
+
+ public void setStartDate(Time date) {
+ startDate = date;
+ }
+
/**
* Converts one of the Calendar.SUNDAY constants to the SU, MO, etc.
* constants. btw, I think we should switch to those here too, to
@@ -118,7 +230,7 @@
throw new RuntimeException("bad day of week: " + day);
}
}
-
+
public static int timeDay2Day(int day)
{
switch (day)
@@ -191,16 +303,16 @@
throw new RuntimeException("bad day of week: " + day);
}
}
-
+
/**
* Converts one of the internal day constants (SU, MO, etc.) to the
* two-letter string representing that constant.
- *
- * @throws IllegalArgumentException Thrown if the day argument is not one of
- * the defined day constants.
- *
+ *
* @param day one the internal constants SU, MO, etc.
* @return the two-letter string for the day ("SU", "MO", etc.)
+ *
+ * @throws IllegalArgumentException Thrown if the day argument is not one of
+ * the defined day constants.
*/
private static String day2String(int day) {
switch (day) {
@@ -283,7 +395,7 @@
s.append(";UNTIL=");
s.append(until);
}
-
+
if (this.count != 0) {
s.append(";COUNT=");
s.append(this.count);
@@ -323,36 +435,484 @@
return s.toString();
}
-
+
public boolean repeatsOnEveryWeekDay() {
if (this.freq != WEEKLY) {
- return false;
+ return false;
}
-
+
int count = this.bydayCount;
if (count != 5) {
return false;
}
-
+
for (int i = 0 ; i < count ; i++) {
int day = byday[i];
if (day == SU || day == SA) {
return false;
}
}
-
+
return true;
}
-
+
public boolean repeatsMonthlyOnDayCount() {
if (this.freq != MONTHLY) {
return false;
}
-
+
if (bydayCount != 1 || bymonthdayCount != 0) {
return false;
}
-
+
return true;
}
+
+ /**
+ * Determines whether two integer arrays contain identical elements.
+ * <p>
+ * The native implementation over-allocated the arrays (and may have stuff left over from
+ * a previous run), so we can't just check the arrays -- the separately-maintained count
+ * field also matters. We assume that a null array will have a count of zero, and that the
+ * array can hold as many elements as the associated count indicates.
+ * <p>
+ * TODO: replace this with Arrays.equals() when the old parser goes away.
+ */
+ private static boolean arraysEqual(int[] array1, int count1, int[] array2, int count2) {
+ if (count1 != count2) {
+ return false;
+ }
+
+ for (int i = 0; i < count1; i++) {
+ if (array1[i] != array2[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof EventRecurrence)) {
+ return false;
+ }
+
+ EventRecurrence er = (EventRecurrence) obj;
+ return (startDate == null ?
+ er.startDate == null : Time.compare(startDate, er.startDate) == 0) &&
+ freq == er.freq &&
+ (until == null ? er.until == null : until.equals(er.until)) &&
+ count == er.count &&
+ interval == er.interval &&
+ wkst == er.wkst &&
+ arraysEqual(bysecond, bysecondCount, er.bysecond, er.bysecondCount) &&
+ arraysEqual(byminute, byminuteCount, er.byminute, er.byminuteCount) &&
+ arraysEqual(byhour, byhourCount, er.byhour, er.byhourCount) &&
+ arraysEqual(byday, bydayCount, er.byday, er.bydayCount) &&
+ arraysEqual(bydayNum, bydayCount, er.bydayNum, er.bydayCount) &&
+ arraysEqual(bymonthday, bymonthdayCount, er.bymonthday, er.bymonthdayCount) &&
+ arraysEqual(byyearday, byyeardayCount, er.byyearday, er.byyeardayCount) &&
+ arraysEqual(byweekno, byweeknoCount, er.byweekno, er.byweeknoCount) &&
+ arraysEqual(bymonth, bymonthCount, er.bymonth, er.bymonthCount) &&
+ arraysEqual(bysetpos, bysetposCount, er.bysetpos, er.bysetposCount);
+ }
+
+ @Override public int hashCode() {
+ // We overrode equals, so we must override hashCode(). Nobody seems to need this though.
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Resets parser-modified fields to their initial state. Does not alter startDate.
+ * <p>
+ * The original parser always set all of the "count" fields, "wkst", and "until",
+ * essentially allowing the same object to be used multiple times by calling parse().
+ * It's unclear whether this behavior was intentional. For now, be paranoid and
+ * preserve the existing behavior by resetting the fields.
+ * <p>
+ * We don't need to touch the integer arrays; they will either be ignored or
+ * overwritten. The "startDate" field is not set by the parser, so we ignore it here.
+ */
+ private void resetFields() {
+ until = null;
+ freq = count = interval = bysecondCount = byminuteCount = byhourCount =
+ bydayCount = bymonthdayCount = byyeardayCount = byweeknoCount = bymonthCount =
+ bysetposCount = 0;
+ }
+
+ /**
+ * Parses an rfc2445 recurrence rule string into its component pieces. Attempting to parse
+ * malformed input will result in an EventRecurrence.InvalidFormatException.
+ *
+ * @param recur The recurrence rule to parse (in un-folded form).
+ */
+ void parse2(String recur) {
+ /*
+ * From RFC 2445 section 4.3.10:
+ *
+ * recur = "FREQ"=freq *(
+ * ; either UNTIL or COUNT may appear in a 'recur',
+ * ; but UNTIL and COUNT MUST NOT occur in the same 'recur'
+ *
+ * ( ";" "UNTIL" "=" enddate ) /
+ * ( ";" "COUNT" "=" 1*DIGIT ) /
+ *
+ * ; the rest of these keywords are optional,
+ * ; but MUST NOT occur more than once
+ *
+ * ( ";" "INTERVAL" "=" 1*DIGIT ) /
+ * ( ";" "BYSECOND" "=" byseclist ) /
+ * ( ";" "BYMINUTE" "=" byminlist ) /
+ * ( ";" "BYHOUR" "=" byhrlist ) /
+ * ( ";" "BYDAY" "=" bywdaylist ) /
+ * ( ";" "BYMONTHDAY" "=" bymodaylist ) /
+ * ( ";" "BYYEARDAY" "=" byyrdaylist ) /
+ * ( ";" "BYWEEKNO" "=" bywknolist ) /
+ * ( ";" "BYMONTH" "=" bymolist ) /
+ * ( ";" "BYSETPOS" "=" bysplist ) /
+ * ( ";" "WKST" "=" weekday ) /
+ * ( ";" x-name "=" text )
+ * )
+ *
+ * Examples:
+ * FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
+ * FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8
+ *
+ * Strategy:
+ * (1) Split the string at ';' boundaries to get an array of rule "parts".
+ * (2) For each part, find substrings for left/right sides of '=' (name/value).
+ * (3) Call a <name>-specific parsing function to parse the <value> into an
+ * output field.
+ *
+ * By keeping track of which names we've seen in a bit vector, we can verify the
+ * constraints indicated above (FREQ appears first, none of them appear more than once --
+ * though x-[name] would require special treatment), and we have either UNTIL or COUNT
+ * but not both.
+ *
+ * In general, RFC 2445 property names (e.g. "FREQ") and enumerations ("TU") must
+ * be handled in a case-insensitive fashion, but case may be significant for other
+ * properties. We don't have any case-sensitive values in RRULE, except possibly
+ * for the custom "X-" properties, but we ignore those anyway. Thus, we can trivially
+ * convert the entire string to upper case and then use simple comparisons.
+ *
+ * Differences from previous version:
+ * - allows lower-case property and enumeration values [optional]
+ * - enforces that FREQ appears first
+ * - enforces that only one of UNTIL and COUNT may be specified
+ * - allows (but ignores) X-* parts
+ * - improved validation on various values (e.g. UNTIL timestamps)
+ * - error messages are more specific
+ */
+
+ /* TODO: replace with "if (freq != 0) throw" if nothing requires this */
+ resetFields();
+
+ int parseFlags = 0;
+ String[] parts;
+ if (ALLOW_LOWER_CASE) {
+ parts = recur.toUpperCase().split(";");
+ } else {
+ parts = recur.split(";");
+ }
+ for (String part : parts) {
+ int equalIndex = part.indexOf('=');
+ if (equalIndex <= 0) {
+ /* no '=' or no LHS */
+ throw new InvalidFormatException("Missing LHS in " + part);
+ }
+
+ String lhs = part.substring(0, equalIndex);
+ String rhs = part.substring(equalIndex + 1);
+ if (rhs.length() == 0) {
+ throw new InvalidFormatException("Missing RHS in " + part);
+ }
+
+ /*
+ * In lieu of a "switch" statement that allows string arguments, we use a
+ * map from strings to parsing functions.
+ */
+ PartParser parser = sParsePartMap.get(lhs);
+ if (parser == null) {
+ if (lhs.startsWith("X-")) {
+ //Log.d(TAG, "Ignoring custom part " + lhs);
+ continue;
+ }
+ throw new InvalidFormatException("Couldn't find parser for " + lhs);
+ } else {
+ int flag = parser.parsePart(rhs, this);
+ if ((parseFlags & flag) != 0) {
+ throw new InvalidFormatException("Part " + lhs + " was specified twice");
+ }
+ if (parseFlags == 0 && flag != PARSED_FREQ) {
+ throw new InvalidFormatException("FREQ must be specified first");
+ }
+ parseFlags |= flag;
+ }
+ }
+
+ // If not specified, week starts on Monday.
+ if ((parseFlags & PARSED_WKST) == 0) {
+ wkst = MO;
+ }
+
+ // FREQ is mandatory.
+ if ((parseFlags & PARSED_FREQ) == 0) {
+ throw new InvalidFormatException("Must specify a FREQ value");
+ }
+
+ // Can't have both UNTIL and COUNT.
+ if ((parseFlags & (PARSED_UNTIL | PARSED_COUNT)) == (PARSED_UNTIL | PARSED_COUNT)) {
+ if (ONLY_ONE_UNTIL_COUNT) {
+ throw new InvalidFormatException("Must not specify both UNTIL and COUNT: " + recur);
+ } else {
+ Log.w(TAG, "Warning: rrule has both UNTIL and COUNT: " + recur);
+ }
+ }
+ }
+
+ /**
+ * Base class for the RRULE part parsers.
+ */
+ abstract static class PartParser {
+ /**
+ * Parses a single part.
+ *
+ * @param value The right-hand-side of the part.
+ * @param er The EventRecurrence into which the result is stored.
+ * @return A bit value indicating which part was parsed.
+ */
+ public abstract int parsePart(String value, EventRecurrence er);
+
+ /**
+ * Parses an integer, with range-checking.
+ *
+ * @param str The string to parse.
+ * @param minVal Minimum allowed value.
+ * @param maxVal Maximum allowed value.
+ * @param allowZero Is 0 allowed?
+ * @return The parsed value.
+ */
+ public static int parseIntRange(String str, int minVal, int maxVal, boolean allowZero) {
+ try {
+ if (str.charAt(0) == '+') {
+ // Integer.parseInt does not allow a leading '+', so skip it manually.
+ str = str.substring(1);
+ }
+ int val = Integer.parseInt(str);
+ if (val < minVal || val > maxVal || (val == 0 && !allowZero)) {
+ throw new InvalidFormatException("Integer value out of range: " + str);
+ }
+ return val;
+ } catch (NumberFormatException nfe) {
+ throw new InvalidFormatException("Invalid integer value: " + str);
+ }
+ }
+
+ /**
+ * Parses a comma-separated list of integers, with range-checking.
+ *
+ * @param listStr The string to parse.
+ * @param minVal Minimum allowed value.
+ * @param maxVal Maximum allowed value.
+ * @param allowZero Is 0 allowed?
+ * @return A new array with values, sized to hold the exact number of elements.
+ */
+ public static int[] parseNumberList(String listStr, int minVal, int maxVal,
+ boolean allowZero) {
+ int[] values;
+
+ if (listStr.indexOf(",") < 0) {
+ // Common case: only one entry, skip split() overhead.
+ values = new int[1];
+ values[0] = parseIntRange(listStr, minVal, maxVal, allowZero);
+ } else {
+ String[] valueStrs = listStr.split(",");
+ int len = valueStrs.length;
+ values = new int[len];
+ for (int i = 0; i < len; i++) {
+ values[i] = parseIntRange(valueStrs[i], minVal, maxVal, allowZero);
+ }
+ }
+ return values;
+ }
+ }
+
+ /** parses FREQ={SECONDLY,MINUTELY,...} */
+ private static class ParseFreq extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ Integer freq = sParseFreqMap.get(value);
+ if (freq == null) {
+ throw new InvalidFormatException("Invalid FREQ value: " + value);
+ }
+ er.freq = freq;
+ return PARSED_FREQ;
+ }
+ }
+ /** parses UNTIL=enddate, e.g. "19970829T021400" */
+ private static class ParseUntil extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ if (VALIDATE_UNTIL) {
+ try {
+ // Parse the time to validate it. The result isn't retained.
+ Time until = new Time();
+ until.parse(value);
+ } catch (TimeFormatException tfe) {
+ throw new InvalidFormatException("Invalid UNTIL value: " + value);
+ }
+ }
+ er.until = value;
+ return PARSED_UNTIL;
+ }
+ }
+ /** parses COUNT=[non-negative-integer] */
+ private static class ParseCount extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ er.count = parseIntRange(value, 0, Integer.MAX_VALUE, true);
+ return PARSED_COUNT;
+ }
+ }
+ /** parses INTERVAL=[non-negative-integer] */
+ private static class ParseInterval extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ er.interval = parseIntRange(value, 1, Integer.MAX_VALUE, false);
+ return PARSED_INTERVAL;
+ }
+ }
+ /** parses BYSECOND=byseclist */
+ private static class ParseBySecond extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] bysecond = parseNumberList(value, 0, 59, true);
+ er.bysecond = bysecond;
+ er.bysecondCount = bysecond.length;
+ return PARSED_BYSECOND;
+ }
+ }
+ /** parses BYMINUTE=byminlist */
+ private static class ParseByMinute extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] byminute = parseNumberList(value, 0, 59, true);
+ er.byminute = byminute;
+ er.byminuteCount = byminute.length;
+ return PARSED_BYMINUTE;
+ }
+ }
+ /** parses BYHOUR=byhrlist */
+ private static class ParseByHour extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] byhour = parseNumberList(value, 0, 23, true);
+ er.byhour = byhour;
+ er.byhourCount = byhour.length;
+ return PARSED_BYHOUR;
+ }
+ }
+ /** parses BYDAY=bywdaylist, e.g. "1SU,-1SU" */
+ private static class ParseByDay extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] byday;
+ int[] bydayNum;
+ int bydayCount;
+
+ if (value.indexOf(",") < 0) {
+ /* only one entry, skip split() overhead */
+ bydayCount = 1;
+ byday = new int[1];
+ bydayNum = new int[1];
+ parseWday(value, byday, bydayNum, 0);
+ } else {
+ String[] wdays = value.split(",");
+ int len = wdays.length;
+ bydayCount = len;
+ byday = new int[len];
+ bydayNum = new int[len];
+ for (int i = 0; i < len; i++) {
+ parseWday(wdays[i], byday, bydayNum, i);
+ }
+ }
+ er.byday = byday;
+ er.bydayNum = bydayNum;
+ er.bydayCount = bydayCount;
+ return PARSED_BYDAY;
+ }
+
+ /** parses [int]weekday, putting the pieces into parallel array entries */
+ private static void parseWday(String str, int[] byday, int[] bydayNum, int index) {
+ int wdayStrStart = str.length() - 2;
+ String wdayStr;
+
+ if (wdayStrStart > 0) {
+ /* number is included; parse it out and advance to weekday */
+ String numPart = str.substring(0, wdayStrStart);
+ int num = parseIntRange(numPart, -53, 53, false);
+ bydayNum[index] = num;
+ wdayStr = str.substring(wdayStrStart);
+ } else {
+ /* just the weekday string */
+ wdayStr = str;
+ }
+ Integer wday = sParseWeekdayMap.get(wdayStr);
+ if (wday == null) {
+ throw new InvalidFormatException("Invalid BYDAY value: " + str);
+ }
+ byday[index] = wday;
+ }
+ }
+ /** parses BYMONTHDAY=bymodaylist */
+ private static class ParseByMonthDay extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] bymonthday = parseNumberList(value, -31, 31, false);
+ er.bymonthday = bymonthday;
+ er.bymonthdayCount = bymonthday.length;
+ return PARSED_BYMONTHDAY;
+ }
+ }
+ /** parses BYYEARDAY=byyrdaylist */
+ private static class ParseByYearDay extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] byyearday = parseNumberList(value, -366, 366, false);
+ er.byyearday = byyearday;
+ er.byyeardayCount = byyearday.length;
+ return PARSED_BYYEARDAY;
+ }
+ }
+ /** parses BYWEEKNO=bywknolist */
+ private static class ParseByWeekNo extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] byweekno = parseNumberList(value, -53, 53, false);
+ er.byweekno = byweekno;
+ er.byweeknoCount = byweekno.length;
+ return PARSED_BYWEEKNO;
+ }
+ }
+ /** parses BYMONTH=bymolist */
+ private static class ParseByMonth extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] bymonth = parseNumberList(value, 1, 12, false);
+ er.bymonth = bymonth;
+ er.bymonthCount = bymonth.length;
+ return PARSED_BYMONTH;
+ }
+ }
+ /** parses BYSETPOS=bysplist */
+ private static class ParseBySetPos extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ int[] bysetpos = parseNumberList(value, Integer.MIN_VALUE, Integer.MAX_VALUE, true);
+ er.bysetpos = bysetpos;
+ er.bysetposCount = bysetpos.length;
+ return PARSED_BYSETPOS;
+ }
+ }
+ /** parses WKST={SU,MO,...} */
+ private static class ParseWkst extends PartParser {
+ @Override public int parsePart(String value, EventRecurrence er) {
+ Integer wkst = sParseWeekdayMap.get(value);
+ if (wkst == null) {
+ throw new InvalidFormatException("Invalid WKST value: " + value);
+ }
+ er.wkst = wkst;
+ return PARSED_WKST;
+ }
+ }
}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 4368e31..3971045 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -17,6 +17,8 @@
package android.provider;
+import com.android.internal.util.ArrayUtils;
+
import android.accounts.Account;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -38,6 +40,8 @@
import android.text.format.Time;
import android.util.Log;
+import java.util.Arrays;
+
/**
* <p>
* The contract between the calendar provider and applications. Contains
@@ -94,19 +98,19 @@
* Broadcast Action: This is the intent that gets fired when an alarm
* notification needs to be posted for a reminder.
*/
- public static final String EVENT_REMINDER_ACTION = "android.intent.action.EVENT_REMINDER";
+ public static final String ACTION_EVENT_REMINDER = "android.intent.action.EVENT_REMINDER";
/**
* Intent Extras key: The start time of an event or an instance of a
* recurring event. (milliseconds since epoch)
*/
- public static final String EVENT_BEGIN_TIME = "beginTime";
+ public static final String EXTRA_EVENT_BEGIN_TIME = "beginTime";
/**
* Intent Extras key: The end time of an event or an instance of a recurring
* event. (milliseconds since epoch)
*/
- public static final String EVENT_END_TIME = "endTime";
+ public static final String EXTRA_EVENT_END_TIME = "endTime";
/**
* This authority is used for writing to or querying from the calendar
@@ -279,7 +283,7 @@
/**
* Columns specific to the Calendars Uri that other Uris can query.
*/
- protected interface CalendarsColumns {
+ protected interface CalendarColumns {
/**
* The color of the calendar
* <P>Type: INTEGER (color value)</P>
@@ -385,7 +389,7 @@
* Class that represents a Calendar Entity. There is one entry per calendar.
* This is a helper class to make batch operations easier.
*/
- public static class CalendarsEntity implements BaseColumns, SyncColumns, CalendarsColumns {
+ public static class CalendarEntity implements BaseColumns, SyncColumns, CalendarColumns {
/**
* The default Uri used when creating a new calendar EntityIterator.
@@ -567,7 +571,7 @@
* <li>{@link #CAL_SYNC10}</li>
* </ul>
*/
- public static class Calendars implements BaseColumns, SyncColumns, CalendarsColumns {
+ public static class Calendars implements BaseColumns, SyncColumns, CalendarColumns {
private static final String WHERE_DELETE_FOR_ACCOUNT = Calendars.ACCOUNT_NAME + "=?"
+ " AND "
+ Calendars.ACCOUNT_TYPE + "=?";
@@ -592,37 +596,6 @@
}
/**
- * Convenience method perform a delete on the Calendar provider. This is
- * a blocking call and should not be used on the UI thread.
- *
- * @param cr the ContentResolver
- * @param selection A filter to apply to rows before deleting, formatted
- * as an SQL WHERE clause (excluding the WHERE itself).
- * @param selectionArgs Fill in the '?'s in the selection
- * @return the count of rows that were deleted
- */
- public static int delete(ContentResolver cr, String selection, String[] selectionArgs)
- {
- return cr.delete(CONTENT_URI, selection, selectionArgs);
- }
-
- /**
- * Convenience method to delete all calendars that match the account.
- * This is a blocking call and should not be used on the UI thread.
- *
- * @param cr the ContentResolver
- * @param account the account whose calendars and events should be
- * deleted
- * @return the count of calendar rows that were deleted
- */
- public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
- // delete all calendars that match this account
- return CalendarContract.Calendars.delete(cr,
- WHERE_DELETE_FOR_ACCOUNT,
- new String[] { account.name, account.type });
- }
-
- /**
* The content:// style URL for accessing Calendars
*/
@SuppressWarnings("hiding")
@@ -764,7 +737,7 @@
/**
* the projection used by the attendees query
*/
- public static final String[] PROJECTION = new String[] {
+ private static final String[] PROJECTION = new String[] {
_ID, ATTENDEE_NAME, ATTENDEE_EMAIL, ATTENDEE_RELATIONSHIP, ATTENDEE_STATUS,};
private static final String ATTENDEES_WHERE = Attendees.EVENT_ID + "=?";
@@ -1444,7 +1417,7 @@
* views into other tables and cannot be changed through the Events table.
*/
public static final class Events implements BaseColumns, SyncColumns, EventsColumns,
- CalendarsColumns {
+ CalendarColumns {
/**
* Queries all events with the given projection. This is a blocking call
@@ -1556,9 +1529,12 @@
* days and minutes. The instances table is not writable and only provides a
* way to query event occurrences.
*/
- public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
+ public static final class Instances implements BaseColumns, EventsColumns, CalendarColumns {
- private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=1";
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=?";
+ private static final String[] WHERE_CALENDARS_ARGS = {
+ "1"
+ };
/**
* Performs a query to return all visible instances in the given range.
@@ -1581,7 +1557,7 @@
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
- null, DEFAULT_SORT_ORDER);
+ WHERE_CALENDARS_ARGS, DEFAULT_SORT_ORDER);
}
/**
@@ -1610,79 +1586,8 @@
ContentUris.appendId(builder, begin);
ContentUris.appendId(builder, end);
builder = builder.appendPath(searchQuery);
- return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED, null,
- DEFAULT_SORT_ORDER);
- }
-
- /**
- * Performs a query to return all visible instances in the given range
- * that match the given selection. This is a blocking function and
- * should not be done on the UI thread. This will cause an expansion of
- * recurring events to fill this time range if they are not already
- * expanded and will slow down for larger time ranges with many
- * recurring events.
- *
- * @param cr The ContentResolver to use for the query
- * @param projection The columns to return
- * @param begin The start of the time range to query in UTC millis since
- * epoch
- * @param end The end of the time range to query in UTC millis since
- * epoch
- * @param selection Filter on the query as an SQL WHERE statement
- * @param selectionArgs Args to replace any '?'s in the selection
- * @param orderBy How to order the rows as an SQL ORDER BY statement
- * @return A Cursor of instances matching the selection
- */
- public static final Cursor query(ContentResolver cr, String[] projection, long begin,
- long end, String selection, String[] selectionArgs, String orderBy) {
- Uri.Builder builder = CONTENT_URI.buildUpon();
- ContentUris.appendId(builder, begin);
- ContentUris.appendId(builder, end);
- if (TextUtils.isEmpty(selection)) {
- selection = WHERE_CALENDARS_SELECTED;
- } else {
- selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
- }
- return cr.query(builder.build(), projection, selection, selectionArgs,
- orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
- }
-
- /**
- * Performs a query to return all visible instances in the given range
- * that match the given selection. This is a blocking function and
- * should not be done on the UI thread. This will cause an expansion of
- * recurring events to fill this time range if they are not already
- * expanded and will slow down for larger time ranges with many
- * recurring events.
- *
- * @param cr The ContentResolver to use for the query
- * @param projection The columns to return
- * @param begin The start of the time range to query in UTC millis since
- * epoch
- * @param end The end of the time range to query in UTC millis since
- * epoch
- * @param searchQuery A string of space separated search terms. Segments
- * enclosed by double quotes will be treated as a single
- * term.
- * @param selection Filter on the query as an SQL WHERE statement
- * @param selectionArgs Args to replace any '?'s in the selection
- * @param orderBy How to order the rows as an SQL ORDER BY statement
- * @return A Cursor of instances matching the selection
- */
- public static final Cursor query(ContentResolver cr, String[] projection, long begin,
- long end, String searchQuery, String selection, String[] selectionArgs,
- String orderBy) {
- Uri.Builder builder = CONTENT_SEARCH_URI.buildUpon();
- ContentUris.appendId(builder, begin);
- ContentUris.appendId(builder, end);
- builder = builder.appendPath(searchQuery);
- if (TextUtils.isEmpty(selection)) {
- selection = WHERE_CALENDARS_SELECTED;
- } else {
- selection = "(" + selection + ") AND " + WHERE_CALENDARS_SELECTED;
- }
- return cr.query(builder.build(), projection, selection, selectionArgs,
- orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
+ return cr.query(builder.build(), projection, WHERE_CALENDARS_SELECTED,
+ WHERE_CALENDARS_ARGS, DEFAULT_SORT_ORDER);
}
/**
@@ -1790,7 +1695,6 @@
*/
public static final Uri URI =
Uri.parse("content://" + AUTHORITY + "/properties");
- public static final String[] POJECTION = { KEY, VALUE };
/**
* If updating a property, this must be provided as the selection. All
@@ -1910,7 +1814,9 @@
/**
* The projection used by the EventDays query.
*/
- public static final String[] PROJECTION = { STARTDAY, ENDDAY };
+ private static final String[] PROJECTION = {
+ STARTDAY, ENDDAY
+ };
private static final String SELECTION = "selected=1";
/**
@@ -1994,7 +1900,7 @@
/**
* The projection used by the reminders query.
*/
- public static final String[] PROJECTION = new String[] {
+ private static final String[] PROJECTION = new String[] {
_ID, MINUTES, METHOD,};
@SuppressWarnings("hiding")
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/reminders");
@@ -2089,7 +1995,7 @@
/**
* Fields and helpers for accessing calendar alerts information. These
* fields are for tracking which alerts have been fired. Scheduled alarms
- * will generate an intent using {@link #EVENT_REMINDER_ACTION}. Apps that
+ * will generate an intent using {@link #ACTION_EVENT_REMINDER}. Apps that
* receive this action may update the {@link #STATE} for the reminder when
* they have finished handling it. Apps that have their notifications
* disabled should not modify the table to ensure that they do not conflict
@@ -2098,7 +2004,7 @@
* state of a reminder.
*/
public static final class CalendarAlerts implements BaseColumns,
- CalendarAlertsColumns, EventsColumns, CalendarsColumns {
+ CalendarAlertsColumns, EventsColumns, CalendarColumns {
/**
* @hide
@@ -2271,7 +2177,7 @@
* keep scheduled reminders up to date but apps may use this to
* implement snooze functionality without modifying the reminders table.
* Scheduled alarms will generate an intent using
- * {@link #EVENT_REMINDER_ACTION}.
+ * {@link #ACTION_EVENT_REMINDER}.
*
* @param context A context for referencing system resources
* @param manager The AlarmManager to use or null
@@ -2290,7 +2196,7 @@
manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
- Intent intent = new Intent(EVENT_REMINDER_ACTION);
+ Intent intent = new Intent(ACTION_EVENT_REMINDER);
intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
intent.putExtra(ALARM_TIME, alarmTime);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 603edf0..afff7e24 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -300,6 +300,21 @@
"android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
/**
+ * Activity Action: Show a dialog to select input method.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_SHOW_INPUT_METHOD_PICKER =
+ "android.settings.SHOW_INPUT_METHOD_PICKER";
+
+ /**
* Activity Action: Show settings to manage the user input dictionary.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -3789,6 +3804,8 @@
"dream_timeout";
/** {@hide} */
+ public static final String NETSTATS_ENABLED = "netstats_enabled";
+ /** {@hide} */
public static final String NETSTATS_POLL_INTERVAL = "netstats_poll_interval";
/** {@hide} */
public static final String NETSTATS_PERSIST_THRESHOLD = "netstats_persist_threshold";
diff --git a/core/java/android/server/BluetoothBondState.java b/core/java/android/server/BluetoothBondState.java
index 39c3c88..5fa8836 100644
--- a/core/java/android/server/BluetoothBondState.java
+++ b/core/java/android/server/BluetoothBondState.java
@@ -79,7 +79,6 @@
mService = service;
mBluetoothInputProfileHandler =
BluetoothInputProfileHandler.getInstance(mContext, mService);
- getProfileProxy();
}
synchronized void setPendingOutgoingBonding(String address) {
@@ -109,6 +108,7 @@
mState.put(mService.getAddressFromObjectPath(device).toUpperCase(),
BluetoothDevice.BOND_BONDED);
}
+ getProfileProxy();
}
public synchronized void setBondState(String address, int state) {
diff --git a/core/java/android/server/BluetoothDeviceProperties.java b/core/java/android/server/BluetoothDeviceProperties.java
index 3dc53d7..fe3ef79 100644
--- a/core/java/android/server/BluetoothDeviceProperties.java
+++ b/core/java/android/server/BluetoothDeviceProperties.java
@@ -35,43 +35,45 @@
mService = service;
}
- synchronized Map<String, String> addProperties(String address,
- String[] properties) {
+ Map<String, String> addProperties(String address, String[] properties) {
/*
* We get a DeviceFound signal every time RSSI changes or name changes.
* Don't create a new Map object every time.
*/
- Map<String, String> propertyValues = mPropertiesMap.get(address);
- if (propertyValues == null) {
- propertyValues = new HashMap<String, String>();
- }
+ Map<String, String> propertyValues;
+ synchronized(mPropertiesMap) {
+ propertyValues = mPropertiesMap.get(address);
+ if (propertyValues == null) {
+ propertyValues = new HashMap<String, String>();
+ }
- for (int i = 0; i < properties.length; i++) {
- String name = properties[i];
- String newValue = null;
- int len;
- if (name == null) {
- Log.e(TAG, "Error: Remote Device Property at index "
+ for (int i = 0; i < properties.length; i++) {
+ String name = properties[i];
+ String newValue = null;
+ int len;
+ if (name == null) {
+ Log.e(TAG, "Error: Remote Device Property at index "
+ i + " is null");
- continue;
- }
- if (name.equals("UUIDs") || name.equals("Nodes")) {
- StringBuilder str = new StringBuilder();
- len = Integer.valueOf(properties[++i]);
- for (int j = 0; j < len; j++) {
- str.append(properties[++i]);
- str.append(",");
+ continue;
}
- if (len > 0) {
- newValue = str.toString();
+ if (name.equals("UUIDs") || name.equals("Nodes")) {
+ StringBuilder str = new StringBuilder();
+ len = Integer.valueOf(properties[++i]);
+ for (int j = 0; j < len; j++) {
+ str.append(properties[++i]);
+ str.append(",");
+ }
+ if (len > 0) {
+ newValue = str.toString();
+ }
+ } else {
+ newValue = properties[++i];
}
- } else {
- newValue = properties[++i];
- }
- propertyValues.put(name, newValue);
+ propertyValues.put(name, newValue);
+ }
+ mPropertiesMap.put(address, propertyValues);
}
- mPropertiesMap.put(address, propertyValues);
// We have added a new remote device or updated its properties.
// Also update the serviceChannel cache.
@@ -79,46 +81,56 @@
return propertyValues;
}
- synchronized void setProperty(String address, String name, String value) {
- Map <String, String> propVal = mPropertiesMap.get(address);
- if (propVal != null) {
- propVal.put(name, value);
- mPropertiesMap.put(address, propVal);
- } else {
- Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
+ void setProperty(String address, String name, String value) {
+ synchronized(mPropertiesMap) {
+ Map <String, String> propVal = mPropertiesMap.get(address);
+ if (propVal != null) {
+ propVal.put(name, value);
+ mPropertiesMap.put(address, propVal);
+ } else {
+ Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
+ }
}
}
- synchronized boolean isInCache(String address) {
- return (mPropertiesMap.get(address) != null);
+ boolean isInCache(String address) {
+ synchronized (mPropertiesMap) {
+ return (mPropertiesMap.get(address) != null);
+ }
}
- synchronized boolean isEmpty() {
- return mPropertiesMap.isEmpty();
+ boolean isEmpty() {
+ synchronized (mPropertiesMap) {
+ return mPropertiesMap.isEmpty();
+ }
}
- synchronized Set<String> keySet() {
- return mPropertiesMap.keySet();
+ Set<String> keySet() {
+ synchronized (mPropertiesMap) {
+ return mPropertiesMap.keySet();
+ }
}
- synchronized String getProperty(String address, String property) {
- Map<String, String> properties = mPropertiesMap.get(address);
- if (properties != null) {
- return properties.get(property);
- } else {
- // Query for remote device properties, again.
- // We will need to reload the cache when we switch Bluetooth on / off
- // or if we crash.
- properties = updateCache(address);
+ String getProperty(String address, String property) {
+ synchronized(mPropertiesMap) {
+ Map<String, String> properties = mPropertiesMap.get(address);
if (properties != null) {
return properties.get(property);
+ } else {
+ // Query for remote device properties, again.
+ // We will need to reload the cache when we switch Bluetooth on / off
+ // or if we crash.
+ properties = updateCache(address);
+ if (properties != null) {
+ return properties.get(property);
+ }
}
}
Log.e(TAG, "getRemoteDeviceProperty: " + property + " not present: " + address);
return null;
}
- synchronized Map<String, String> updateCache(String address) {
+ Map<String, String> updateCache(String address) {
String[] propValues = mService.getRemoteDeviceProperties(address);
if (propValues != null) {
return addProperties(address, propValues);
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index d594567..519b980 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -119,13 +119,37 @@
* being applied.
* @hide
*/
- public int unscaledWidthPixels;
+ public int noncompatWidthPixels;
/**
* The reported display height prior to any compatibility mode scaling
* being applied.
* @hide
*/
- public int unscaledHeightPixels;
+ public int noncompatHeightPixels;
+ /**
+ * The reported display density prior to any compatibility mode scaling
+ * being applied.
+ * @hide
+ */
+ public float noncompatDensity;
+ /**
+ * The reported scaled density prior to any compatibility mode scaling
+ * being applied.
+ * @hide
+ */
+ public float noncompatScaledDensity;
+ /**
+ * The reported display xdpi prior to any compatibility mode scaling
+ * being applied.
+ * @hide
+ */
+ public float noncompatXdpi;
+ /**
+ * The reported display ydpi prior to any compatibility mode scaling
+ * being applied.
+ * @hide
+ */
+ public float noncompatYdpi;
public DisplayMetrics() {
}
@@ -138,8 +162,12 @@
scaledDensity = o.scaledDensity;
xdpi = o.xdpi;
ydpi = o.ydpi;
- unscaledWidthPixels = o.unscaledWidthPixels;
- unscaledHeightPixels = o.unscaledHeightPixels;
+ noncompatWidthPixels = o.noncompatWidthPixels;
+ noncompatHeightPixels = o.noncompatHeightPixels;
+ noncompatDensity = o.noncompatDensity;
+ noncompatScaledDensity = o.noncompatScaledDensity;
+ noncompatXdpi = o.noncompatXdpi;
+ noncompatYdpi = o.noncompatYdpi;
}
public void setToDefaults() {
@@ -150,8 +178,8 @@
scaledDensity = density;
xdpi = DENSITY_DEVICE;
ydpi = DENSITY_DEVICE;
- unscaledWidthPixels = 0;
- unscaledHeightPixels = 0;
+ noncompatWidthPixels = 0;
+ noncompatHeightPixels = 0;
}
@Override
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 3fa8dfd..2be5a49 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -107,8 +107,8 @@
CompatibilityInfo ci;
if (doCompat && (ci=mCompatibilityInfo.getIfNeeded()) != null) {
synchronized (mTmpMetrics) {
- mTmpMetrics.unscaledWidthPixels = outSize.x;
- mTmpMetrics.unscaledHeightPixels = outSize.y;
+ mTmpMetrics.noncompatWidthPixels = outSize.x;
+ mTmpMetrics.noncompatHeightPixels = outSize.y;
mTmpMetrics.density = mDensity;
ci.applyToDisplayMetrics(mTmpMetrics);
outSize.x = mTmpMetrics.widthPixels;
@@ -268,14 +268,15 @@
}
private void getNonSizeMetrics(DisplayMetrics outMetrics) {
- outMetrics.density = mDensity;
outMetrics.densityDpi = (int)((mDensity*DisplayMetrics.DENSITY_DEFAULT)+.5f);
- outMetrics.scaledDensity= outMetrics.density;
- outMetrics.xdpi = mDpiX;
- outMetrics.ydpi = mDpiY;
- outMetrics.unscaledWidthPixels = outMetrics.widthPixels;
- outMetrics.unscaledHeightPixels = outMetrics.heightPixels;
+ outMetrics.noncompatWidthPixels = outMetrics.widthPixels;
+ outMetrics.noncompatHeightPixels = outMetrics.heightPixels;
+
+ outMetrics.density = outMetrics.noncompatDensity = mDensity;
+ outMetrics.scaledDensity = outMetrics.noncompatScaledDensity = outMetrics.density;
+ outMetrics.xdpi = outMetrics.noncompatXdpi = mDpiX;
+ outMetrics.ydpi = outMetrics.noncompatYdpi = mDpiY;
}
static IWindowManager getWindowManager() {
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 5216c49..04f35dc 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -161,10 +161,11 @@
// Hardware layers
///////////////////////////////////////////////////////////////////////////
- static native int nCreateTextureLayer(int[] layerInfo);
+ static native int nCreateTextureLayer(boolean opaque, int[] layerInfo);
static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo);
- static native void nUpdateTextureLayer(int layerId, int width, int height, SurfaceTexture surface);
+ static native void nUpdateTextureLayer(int layerId, int width, int height, boolean opaque,
+ SurfaceTexture surface);
static native void nDestroyLayer(int layerId);
static native void nDestroyLayerDeferred(int layerId);
static native boolean nCopyLayer(int layerId, int bitmap);
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index 063eee7..5ee292e 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -28,9 +28,9 @@
private int mTexture;
private SurfaceTexture mSurface;
- GLES20TextureLayer() {
+ GLES20TextureLayer(boolean isOpaque) {
int[] layerInfo = new int[2];
- mLayer = GLES20Canvas.nCreateTextureLayer(layerInfo);
+ mLayer = GLES20Canvas.nCreateTextureLayer(isOpaque, layerInfo);
if (mLayer != 0) {
mTexture = layerInfo[0];
@@ -65,12 +65,12 @@
SurfaceTexture getSurfaceTexture() {
if (mSurface == null) {
- mSurface = new SurfaceTexture(mTexture);
+ mSurface = new SurfaceTexture(mTexture, false);
}
return mSurface;
}
- void update(int width, int height) {
- GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, mSurface);
+ void update(int width, int height, boolean isOpaque) {
+ GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface);
}
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5ceb12a..c6d011e 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -170,9 +170,11 @@
* Creates a new hardware layer. A hardware layer built by calling this
* method will be treated as a texture layer, instead of as a render target.
*
+ * @param isOpaque Whether the layer should be opaque or not
+ *
* @return A hardware layer
*/
- abstract HardwareLayer createHardwareLayer();
+ abstract HardwareLayer createHardwareLayer(boolean isOpaque);
/**
* Creates a new hardware layer.
@@ -202,8 +204,9 @@
* @param layer The hardware layer to update
* @param width The layer's width
* @param height The layer's height
+ * @param isOpaque Whether the layer is opaque
*/
- abstract void updateTextureLayer(HardwareLayer layer, int width, int height);
+ abstract void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque);
/**
* Copies the content of the specified layer into the specified bitmap.
@@ -803,8 +806,8 @@
}
@Override
- HardwareLayer createHardwareLayer() {
- return new GLES20TextureLayer();
+ HardwareLayer createHardwareLayer(boolean isOpaque) {
+ return new GLES20TextureLayer(isOpaque);
}
@Override
@@ -818,8 +821,8 @@
}
@Override
- void updateTextureLayer(HardwareLayer layer, int width, int height) {
- ((GLES20TextureLayer) layer).update(width, height);
+ void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque) {
+ ((GLES20TextureLayer) layer).update(width, height, isOpaque);
}
@Override
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index e14b975..49f3bbe 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -32,6 +32,11 @@
public final class InputEventConsistencyVerifier {
private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
+ private static final String EVENT_TYPE_KEY = "KeyEvent";
+ private static final String EVENT_TYPE_TRACKBALL = "TrackballEvent";
+ private static final String EVENT_TYPE_TOUCH = "TouchEvent";
+ private static final String EVENT_TYPE_GENERIC_MOTION = "GenericMotionEvent";
+
// The number of recent events to log when a problem is detected.
// Can be set to 0 to disable logging recent events but the runtime overhead of
// this feature is negligible on current hardware.
@@ -54,6 +59,7 @@
// It does not make sense to examine the contents of the last event since it may have
// been recycled.
private InputEvent mLastEvent;
+ private String mLastEventType;
private int mLastNestingLevel;
// Copy of the most recent events.
@@ -185,7 +191,7 @@
* and both dispatching methods call into the consistency verifier.
*/
public void onKeyEvent(KeyEvent event, int nestingLevel) {
- if (!startEvent(event, nestingLevel, "KeyEvent")) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_KEY)) {
return;
}
@@ -247,7 +253,7 @@
* and both dispatching methods call into the consistency verifier.
*/
public void onTrackballEvent(MotionEvent event, int nestingLevel) {
- if (!startEvent(event, nestingLevel, "TrackballEvent")) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_TRACKBALL)) {
return;
}
@@ -310,23 +316,19 @@
* and both dispatching methods call into the consistency verifier.
*/
public void onTouchEvent(MotionEvent event, int nestingLevel) {
- if (!startEvent(event, nestingLevel, "TouchEvent")) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_TOUCH)) {
return;
}
final int action = event.getAction();
final boolean newStream = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_CANCEL;
- if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
- if (newStream) {
- mTouchEventStreamIsTainted = false;
- mTouchEventStreamUnhandled = false;
- mTouchEventStreamPointers = 0;
- } else {
- finishEvent(mTouchEventStreamIsTainted);
- return;
- }
+ if (newStream && (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled)) {
+ mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
}
+ final boolean wasTainted = mTouchEventStreamIsTainted;
try {
ensureMetaStateIsNormalized(event.getMetaState());
@@ -439,7 +441,7 @@
problem("Source was not SOURCE_CLASS_POINTER.");
}
} finally {
- finishEvent(false);
+ finishEvent(wasTainted);
}
}
@@ -453,7 +455,7 @@
* and both dispatching methods call into the consistency verifier.
*/
public void onGenericMotionEvent(MotionEvent event, int nestingLevel) {
- if (!startEvent(event, nestingLevel, "GenericMotionEvent")) {
+ if (!startEvent(event, nestingLevel, EVENT_TYPE_GENERIC_MOTION)) {
return;
}
@@ -568,21 +570,19 @@
}
private boolean startEvent(InputEvent event, int nestingLevel, String eventType) {
- // Ignore the event if it is already tainted.
- if (event.isTainted()) {
- return false;
- }
-
// Ignore the event if we already checked it at a higher nesting level.
- if (event == mLastEvent && nestingLevel < mLastNestingLevel) {
+ if (event == mLastEvent && nestingLevel < mLastNestingLevel
+ && eventType == mLastEventType) {
return false;
}
if (nestingLevel > 0) {
mLastEvent = event;
+ mLastEventType = eventType;
mLastNestingLevel = nestingLevel;
} else {
mLastEvent = null;
+ mLastEventType = null;
mLastNestingLevel = 0;
}
@@ -593,27 +593,30 @@
private void finishEvent(boolean tainted) {
if (mViolationMessage != null && mViolationMessage.length() != 0) {
- mViolationMessage.append("\n in ").append(mCaller);
- mViolationMessage.append("\n ");
- appendEvent(mViolationMessage, 0, mCurrentEvent, false);
+ if (!tainted) {
+ // Write a log message only if the event was not already tainted.
+ mViolationMessage.append("\n in ").append(mCaller);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
- if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
- mViolationMessage.append("\n -- recent events --");
- for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
- final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
- % RECENT_EVENTS_TO_LOG;
- final InputEvent event = mRecentEvents[index];
- if (event == null) {
- break;
+ if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
+ mViolationMessage.append("\n -- recent events --");
+ for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
+ final int index = (mMostRecentEventIndex + RECENT_EVENTS_TO_LOG - i)
+ % RECENT_EVENTS_TO_LOG;
+ final InputEvent event = mRecentEvents[index];
+ if (event == null) {
+ break;
+ }
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
}
- mViolationMessage.append("\n ");
- appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
}
- }
- Log.d(mLogTag, mViolationMessage.toString());
+ Log.d(mLogTag, mViolationMessage.toString());
+ tainted = true;
+ }
mViolationMessage.setLength(0);
- tainted = true;
}
if (tainted) {
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index 0421205..de398eb 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -67,15 +67,19 @@
* // Something bad happened
* }
* }
- *
+ *
* public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
* // Ignored, Camera does all the work for us
* }
- *
+ *
* public void onSurfaceTextureDestroyed(SurfaceTexture surface) {
* mCamera.stopPreview();
* mCamera.release();
* }
+ *
+ * public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ * // Ignored
+ * }
* }
* </pre>
*
@@ -96,6 +100,8 @@
private SurfaceTexture mSurface;
private SurfaceTextureListener mListener;
+ private boolean mOpaque = true;
+
private final Runnable mUpdateLayerAction = new Runnable() {
@Override
public void run() {
@@ -146,6 +152,28 @@
mLayerPaint = new Paint();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isOpaque() {
+ return mOpaque;
+ }
+
+ /**
+ * Indicates whether the content of this TextureView is opaque. The
+ * content is assumed to be opaque by default.
+ *
+ * @param opaque True if the content of this TextureView is opaque,
+ * false otherwise
+ */
+ public void setOpaque(boolean opaque) {
+ if (opaque != mOpaque) {
+ mOpaque = opaque;
+ if (mLayer != null) updateLayer();
+ }
+ }
+
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
@@ -215,7 +243,6 @@
*/
@Override
public final void draw(Canvas canvas) {
- super.draw(canvas);
}
/**
@@ -246,7 +273,7 @@
}
if (mLayer == null) {
- mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer();
+ mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
@@ -290,7 +317,11 @@
return;
}
- mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight());
+ mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(), mOpaque);
+
+ if (mListener != null) {
+ mListener.onSurfaceTextureUpdated(mSurface);
+ }
invalidate();
}
@@ -451,6 +482,14 @@
* @param surface The surface about to be destroyed
*/
public void onSurfaceTextureDestroyed(SurfaceTexture surface);
+
+ /**
+ * Invoked when the specified {@link SurfaceTexture} is updated through
+ * {@link SurfaceTexture#updateTexImage()}.
+ *
+ * @param surface The surface just updated
+ */
+ public void onSurfaceTextureUpdated(SurfaceTexture surface);
}
private static native void nSetDefaultBufferSize(SurfaceTexture surfaceTexture,
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4403591..8184643 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2262,6 +2262,8 @@
private OnTouchListener mOnTouchListener;
+ private OnHoverListener mOnHoverListener;
+
private OnGenericMotionListener mOnGenericMotionListener;
private OnDragListener mOnDragListener;
@@ -2484,6 +2486,12 @@
Rect mLocalDirtyRect;
/**
+ * Set to true when the view is sending hover accessibility events because it
+ * is the innermost hovered view.
+ */
+ private boolean mSendingHoverAccessibilityEvents;
+
+ /**
* Consistency verifier for debugging purposes.
* @hide
*/
@@ -2503,6 +2511,9 @@
mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | LAYOUT_DIRECTION_INHERIT;
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
+ mUserPaddingStart = -1;
+ mUserPaddingEnd = -1;
+ mUserPaddingRelative = false;
}
/**
@@ -2864,13 +2875,16 @@
mUserPaddingRelative = (startPadding >= 0 || endPadding >= 0);
+ // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved
+ // layout direction). Those cached values will be used later during padding resolution.
+ mUserPaddingStart = startPadding;
+ mUserPaddingEnd = endPadding;
+
if (padding >= 0) {
leftPadding = padding;
topPadding = padding;
rightPadding = padding;
bottomPadding = padding;
- startPadding = padding;
- endPadding = padding;
}
// If the user specified the padding (either with android:padding or
@@ -2882,11 +2896,6 @@
rightPadding >= 0 ? rightPadding : mPaddingRight,
bottomPadding >= 0 ? bottomPadding : mPaddingBottom);
- // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved
- // layout direction). Those cached values will be used later during padding resolution.
- mUserPaddingStart = startPadding;
- mUserPaddingEnd = endPadding;
-
if (viewFlagMasks != 0) {
setFlags(viewFlagValues, viewFlagMasks);
}
@@ -5117,9 +5126,6 @@
return true;
}
- if (mInputEventConsistencyVerifier != null) {
- mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
- }
return false;
}
@@ -5147,6 +5153,12 @@
|| action == MotionEvent.ACTION_HOVER_MOVE
|| action == MotionEvent.ACTION_HOVER_EXIT) {
if (dispatchHoverEvent(event)) {
+ // For compatibility with existing applications that handled HOVER_MOVE
+ // events in onGenericMotionEvent, dispatch the event there. The
+ // onHoverEvent method did not exist at the time.
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ dispatchGenericMotionEventInternal(event);
+ }
return true;
}
} else if (dispatchGenericPointerEvent(event)) {
@@ -5156,6 +5168,17 @@
return true;
}
+ if (dispatchGenericMotionEventInternal(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
+ }
+
+ private boolean dispatchGenericMotionEventInternal(MotionEvent event) {
//noinspection SimplifiableIfStatement
if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
@@ -5181,13 +5204,42 @@
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
- * @hide
*/
protected boolean dispatchHoverEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ if (!hasHoveredChild() && !mSendingHoverAccessibilityEvents) {
+ mSendingHoverAccessibilityEvents = true;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ }
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ if (mSendingHoverAccessibilityEvents) {
+ mSendingHoverAccessibilityEvents = false;
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ }
+ break;
+ }
+
+ if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && mOnHoverListener.onHover(this, event)) {
+ return true;
+ }
+
return onHoverEvent(event);
}
/**
+ * Returns true if the view has a child to which it has recently sent
+ * {@link MotionEvent#ACTION_HOVER_ENTER}. If this view is hovered and
+ * it does not have a hovered child, then it must be the innermost hovered view.
+ * @hide
+ */
+ protected boolean hasHoveredChild() {
+ return false;
+ }
+
+ /**
* Dispatch a generic motion event to the view under the first pointer.
* <p>
* Do not call this method directly.
@@ -5196,7 +5248,6 @@
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
- * @hide
*/
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
return false;
@@ -5211,7 +5262,6 @@
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
- * @hide
*/
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
return false;
@@ -5788,71 +5838,129 @@
/**
* Implement this method to handle hover events.
* <p>
- * Hover events are pointer events with action {@link MotionEvent#ACTION_HOVER_ENTER},
- * {@link MotionEvent#ACTION_HOVER_MOVE}, or {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * This method is called whenever a pointer is hovering into, over, or out of the
+ * bounds of a view and the view is not currently being touched.
+ * Hover events are represented as pointer events with action
+ * {@link MotionEvent#ACTION_HOVER_ENTER}, {@link MotionEvent#ACTION_HOVER_MOVE},
+ * or {@link MotionEvent#ACTION_HOVER_EXIT}.
+ * </p>
+ * <ul>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_ENTER}
+ * when the pointer enters the bounds of the view.</li>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_MOVE}
+ * when the pointer has already entered the bounds of the view and has moved.</li>
+ * <li>The view receives a hover event with action {@link MotionEvent#ACTION_HOVER_EXIT}
+ * when the pointer has exited the bounds of the view or when the pointer is
+ * about to go down due to a button click, tap, or similar user action that
+ * causes the view to be touched.</li>
+ * </ul>
+ * <p>
+ * The view should implement this method to return true to indicate that it is
+ * handling the hover event, such as by changing its drawable state.
* </p><p>
- * The view receives hover enter as the pointer enters the bounds of the view and hover
- * exit as the pointer exits the bound of the view or just before the pointer goes down
- * (which implies that {@link #onTouchEvent(MotionEvent)} will be called soon).
- * </p><p>
- * If the view would like to handle the hover event itself and prevent its children
- * from receiving hover, it should return true from this method. If this method returns
- * true and a child has already received a hover enter event, the child will
- * automatically receive a hover exit event.
- * </p><p>
- * The default implementation sets the hovered state of the view if the view is
- * clickable.
+ * The default implementation calls {@link #setHovered} to update the hovered state
+ * of the view when a hover enter or hover exit event is received, if the view
+ * is enabled and is clickable.
* </p>
*
* @param event The motion event that describes the hover.
- * @return True if this view handled the hover event and does not want its children
- * to receive the hover event.
+ * @return True if the view handled the hover event.
+ *
+ * @see #isHovered
+ * @see #setHovered
+ * @see #onHoverChanged
*/
public boolean onHoverEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- setHovered(true);
- break;
+ if (isHoverable()) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ setHovered(true);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ setHovered(false);
+ break;
+ }
+ return true;
+ }
+ return false;
+ }
- case MotionEvent.ACTION_HOVER_EXIT:
- setHovered(false);
- break;
+ /**
+ * Returns true if the view should handle {@link #onHoverEvent}
+ * by calling {@link #setHovered} to change its hovered state.
+ *
+ * @return True if the view is hoverable.
+ */
+ private boolean isHoverable() {
+ final int viewFlags = mViewFlags;
+ if ((viewFlags & ENABLED_MASK) == DISABLED) {
+ return false;
}
- return false;
+ return (viewFlags & CLICKABLE) == CLICKABLE
+ || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
}
/**
* Returns true if the view is currently hovered.
*
* @return True if the view is currently hovered.
+ *
+ * @see #setHovered
+ * @see #onHoverChanged
*/
+ @ViewDebug.ExportedProperty
public boolean isHovered() {
return (mPrivateFlags & HOVERED) != 0;
}
/**
* Sets whether the view is currently hovered.
+ * <p>
+ * Calling this method also changes the drawable state of the view. This
+ * enables the view to react to hover by using different drawable resources
+ * to change its appearance.
+ * </p><p>
+ * The {@link #onHoverChanged} method is called when the hovered state changes.
+ * </p>
*
* @param hovered True if the view is hovered.
+ *
+ * @see #isHovered
+ * @see #onHoverChanged
*/
public void setHovered(boolean hovered) {
if (hovered) {
if ((mPrivateFlags & HOVERED) == 0) {
mPrivateFlags |= HOVERED;
refreshDrawableState();
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
+ onHoverChanged(true);
}
} else {
if ((mPrivateFlags & HOVERED) != 0) {
mPrivateFlags &= ~HOVERED;
refreshDrawableState();
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
+ onHoverChanged(false);
}
}
}
/**
+ * Implement this method to handle hover state changes.
+ * <p>
+ * This method is called whenever the hover state changes as a result of a
+ * call to {@link #setHovered}.
+ * </p>
+ *
+ * @param hovered The current hover state, as returned by {@link #isHovered}.
+ *
+ * @see #isHovered
+ * @see #setHovered
+ */
+ public void onHoverChanged(boolean hovered) {
+ }
+
+ /**
* Implement this method to handle touch screen motion events.
*
* @param event The motion event.
@@ -8860,12 +8968,12 @@
// Start user padding override Left user padding. Otherwise, if Left user
// padding is not defined, use the default left padding. If Left user padding
// is defined, just use it.
- if (mUserPaddingStart >= 0) {
+ if (mUserPaddingStart > 0) {
mUserPaddingLeft = mUserPaddingStart;
} else if (mUserPaddingLeft < 0) {
mUserPaddingLeft = mPaddingLeft;
}
- if (mUserPaddingEnd >= 0) {
+ if (mUserPaddingEnd > 0) {
mUserPaddingRight = mUserPaddingEnd;
} else if (mUserPaddingRight < 0) {
mUserPaddingRight = mPaddingRight;
@@ -9566,10 +9674,10 @@
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
+ mPrivateFlags &= ~DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- mPrivateFlags &= ~DIRTY_MASK;
dispatchDraw(canvas);
} else {
draw(canvas);
@@ -11026,6 +11134,10 @@
*/
public void setPaddingRelative(int start, int top, int end, int bottom) {
mUserPaddingRelative = true;
+
+ mUserPaddingStart = start;
+ mUserPaddingEnd = end;
+
switch(getResolvedLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
setPadding(end, top, start, bottom);
@@ -13097,6 +13209,24 @@
}
/**
+ * Interface definition for a callback to be invoked when a hover event is
+ * dispatched to this view. The callback will be invoked before the hover
+ * event is given to the view.
+ */
+ public interface OnHoverListener {
+ /**
+ * Called when a hover event is dispatched to a view. This allows listeners to
+ * get a chance to respond before the target view.
+ *
+ * @param v The view the hover event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onHover(View v, MotionEvent event);
+ }
+
+ /**
* Interface definition for a callback to be invoked when a generic motion event is
* dispatched to this view. The callback will be invoked before the generic motion
* event is given to the view.
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index afbedaf..ad660c1 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -288,6 +288,10 @@
private final int mDensity;
+ // This flag tracks when the mIgnoreDirtyState flag is set during draw(), to avoid
+ // clearing that flag prematurely
+ private boolean mSetIgnoreDirtyState = false;
+
/**
* Consistency verifier for debugging purposes.
*/
@@ -672,6 +676,7 @@
}
}
if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
+ mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
mDirty.union(dirty);
@@ -1000,8 +1005,7 @@
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
- if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
- || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
@@ -1748,7 +1752,7 @@
mAttachInfo.mIgnoreDirtyState = true;
dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
-
+
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
if (!dirty.isEmpty() || mIsAnimating) {
mIsAnimating = false;
@@ -1878,9 +1882,13 @@
}
canvas.setScreenDensity(scalingRequired
? DisplayMetrics.DENSITY_DEVICE : 0);
+ mSetIgnoreDirtyState = false;
mView.draw(canvas);
} finally {
- mAttachInfo.mIgnoreDirtyState = false;
+ if (!mSetIgnoreDirtyState) {
+ // Only clear the flag if it was not set during the mView.draw() call
+ mAttachInfo.mIgnoreDirtyState = false;
+ }
}
if (false && ViewDebug.consistencyCheckEnabled) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index a6bce75..b9474e6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -143,8 +143,16 @@
@ViewDebug.ExportedProperty(category = "events")
private float mLastTouchDownY;
- // Child which last received ACTION_HOVER_ENTER and ACTION_HOVER_MOVE.
- private View mHoveredChild;
+ // First hover target in the linked list of hover targets.
+ // The hover targets are children which have received ACTION_HOVER_ENTER.
+ // They might not have actually handled the hover event, but we will
+ // continue sending hover events to them as long as the pointer remains over
+ // their bounds and the view group does not intercept hover.
+ private HoverTarget mFirstHoverTarget;
+
+ // True if the view group itself received a hover event.
+ // It might not have actually handled the hover event.
+ private boolean mHoveredSelf;
/**
* Internal flags.
@@ -1222,56 +1230,32 @@
return false;
}
- /** @hide */
+ /**
+ * {@inheritDoc}
+ */
+ @SuppressWarnings({"ConstantConditions"})
@Override
protected boolean dispatchHoverEvent(MotionEvent event) {
- // Send the hover enter or hover move event to the view group first.
- // If it handles the event then a hovered child should receive hover exit.
- boolean handled = false;
- final boolean interceptHover;
final int action = event.getAction();
- if (action == MotionEvent.ACTION_HOVER_EXIT) {
- interceptHover = true;
- } else {
- handled = super.dispatchHoverEvent(event);
- interceptHover = handled;
- }
- // Send successive hover events to the hovered child as long as the pointer
- // remains within the child's bounds.
+ // First check whether the view group wants to intercept the hover event.
+ final boolean interceptHover = onInterceptHoverEvent(event);
+ event.setAction(action); // restore action in case it was changed
+
MotionEvent eventNoHistory = event;
- if (mHoveredChild != null) {
+ boolean handled = false;
+
+ // Send events to the hovered children and build a new list of hover targets until
+ // one is found that handles the event.
+ HoverTarget firstOldHoverTarget = mFirstHoverTarget;
+ mFirstHoverTarget = null;
+ if (!interceptHover && action != MotionEvent.ACTION_HOVER_EXIT) {
final float x = event.getX();
final float y = event.getY();
-
- if (interceptHover
- || !isTransformedTouchPointInView(x, y, mHoveredChild, null)) {
- // Pointer exited the child.
- // Send it a hover exit with only the most recent coordinates. We could
- // try to find the exact point in history when the pointer left the view
- // but it is not worth the effort.
- eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
- eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
- handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, mHoveredChild);
- eventNoHistory.setAction(action);
- mHoveredChild = null;
- } else {
- // Pointer is still within the child.
- //noinspection ConstantConditions
- handled |= dispatchTransformedGenericPointerEvent(event, mHoveredChild);
- }
- }
-
- // Find a new hovered child if needed.
- if (!interceptHover && mHoveredChild == null
- && (action == MotionEvent.ACTION_HOVER_ENTER
- || action == MotionEvent.ACTION_HOVER_MOVE)) {
final int childrenCount = mChildrenCount;
if (childrenCount != 0) {
final View[] children = mChildren;
- final float x = event.getX();
- final float y = event.getY();
-
+ HoverTarget lastHoverTarget = null;
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
if (!canViewReceivePointerEvents(child)
@@ -1279,24 +1263,140 @@
continue;
}
- // Found the hovered child.
- mHoveredChild = child;
- if (action == MotionEvent.ACTION_HOVER_MOVE) {
- // Pointer was moving within the view group and entered the child.
- // Send it a hover enter and hover move with only the most recent
- // coordinates. We could try to find the exact point in history when
- // the pointer entered the view but it is not worth the effort.
- eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
- eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
- handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
- eventNoHistory.setAction(action);
+ // Obtain a hover target for this child. Dequeue it from the
+ // old hover target list if the child was previously hovered.
+ HoverTarget hoverTarget = firstOldHoverTarget;
+ final boolean wasHovered;
+ for (HoverTarget predecessor = null; ;) {
+ if (hoverTarget == null) {
+ hoverTarget = HoverTarget.obtain(child);
+ wasHovered = false;
+ break;
+ }
- handled |= dispatchTransformedGenericPointerEvent(eventNoHistory, child);
- } else { /* must be ACTION_HOVER_ENTER */
- // Pointer entered the child.
- handled |= dispatchTransformedGenericPointerEvent(event, child);
+ if (hoverTarget.child == child) {
+ if (predecessor != null) {
+ predecessor.next = hoverTarget.next;
+ } else {
+ firstOldHoverTarget = hoverTarget.next;
+ }
+ hoverTarget.next = null;
+ wasHovered = true;
+ break;
+ }
+
+ predecessor = hoverTarget;
+ hoverTarget = hoverTarget.next;
}
- break;
+
+ // Enqueue the hover target onto the new hover target list.
+ if (lastHoverTarget != null) {
+ lastHoverTarget.next = hoverTarget;
+ } else {
+ lastHoverTarget = hoverTarget;
+ mFirstHoverTarget = hoverTarget;
+ }
+
+ // Dispatch the event to the child.
+ if (action == MotionEvent.ACTION_HOVER_ENTER) {
+ if (!wasHovered) {
+ // Send the enter as is.
+ handled |= dispatchTransformedGenericPointerEvent(
+ event, child); // enter
+ }
+ } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ if (!wasHovered) {
+ // Synthesize an enter from a move.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // enter
+ eventNoHistory.setAction(action);
+
+ handled |= dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // move
+ } else {
+ // Send the move as is.
+ handled |= dispatchTransformedGenericPointerEvent(event, child);
+ }
+ }
+ if (handled) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Send exit events to all previously hovered children that are no longer hovered.
+ while (firstOldHoverTarget != null) {
+ final View child = firstOldHoverTarget.child;
+
+ // Exit the old hovered child.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ // Send the exit as is.
+ handled |= dispatchTransformedGenericPointerEvent(
+ event, child); // exit
+ } else {
+ // Synthesize an exit from a move or enter.
+ // Ignore the result because hover focus has moved to a different view.
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ dispatchTransformedGenericPointerEvent(
+ event, child); // move
+ }
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ dispatchTransformedGenericPointerEvent(
+ eventNoHistory, child); // exit
+ eventNoHistory.setAction(action);
+ }
+
+ final HoverTarget nextOldHoverTarget = firstOldHoverTarget.next;
+ firstOldHoverTarget.recycle();
+ firstOldHoverTarget = nextOldHoverTarget;
+ }
+
+ // Send events to the view group itself if no children have handled it.
+ boolean newHoveredSelf = !handled;
+ if (newHoveredSelf == mHoveredSelf) {
+ if (newHoveredSelf) {
+ // Send event to the view group as before.
+ handled |= super.dispatchHoverEvent(event);
+ }
+ } else {
+ if (mHoveredSelf) {
+ // Exit the view group.
+ if (action == MotionEvent.ACTION_HOVER_EXIT) {
+ // Send the exit as is.
+ handled |= super.dispatchHoverEvent(event); // exit
+ } else {
+ // Synthesize an exit from a move or enter.
+ // Ignore the result because hover focus is moving to a different view.
+ if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ super.dispatchHoverEvent(event); // move
+ }
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_EXIT);
+ super.dispatchHoverEvent(eventNoHistory); // exit
+ eventNoHistory.setAction(action);
+ }
+ mHoveredSelf = false;
+ }
+
+ if (newHoveredSelf) {
+ // Enter the view group.
+ if (action == MotionEvent.ACTION_HOVER_ENTER) {
+ // Send the enter as is.
+ handled |= super.dispatchHoverEvent(event); // enter
+ mHoveredSelf = true;
+ } else if (action == MotionEvent.ACTION_HOVER_MOVE) {
+ // Synthesize an enter from a move.
+ eventNoHistory = obtainMotionEventNoHistoryOrSelf(eventNoHistory);
+ eventNoHistory.setAction(MotionEvent.ACTION_HOVER_ENTER);
+ handled |= super.dispatchHoverEvent(eventNoHistory); // enter
+ eventNoHistory.setAction(action);
+
+ handled |= super.dispatchHoverEvent(eventNoHistory); // move
+ mHoveredSelf = true;
}
}
}
@@ -1306,25 +1406,55 @@
eventNoHistory.recycle();
}
- // Send hover exit to the view group. If there was a child, we will already have
- // sent the hover exit to it.
- if (action == MotionEvent.ACTION_HOVER_EXIT) {
- handled |= super.dispatchHoverEvent(event);
- }
-
// Done.
return handled;
}
+ /** @hide */
@Override
- public boolean onHoverEvent(MotionEvent event) {
- // Handle the event only if leaf. This guarantees that
- // the leafs (or any custom class that returns true from
- // this method) will get a change to process the hover.
- //noinspection SimplifiableIfStatement
- if (getChildCount() == 0) {
- return super.onHoverEvent(event);
- }
+ protected boolean hasHoveredChild() {
+ return mFirstHoverTarget != null;
+ }
+
+ /**
+ * Implement this method to intercept hover events before they are handled
+ * by child views.
+ * <p>
+ * This method is called before dispatching a hover event to a child of
+ * the view group or to the view group's own {@link #onHoverEvent} to allow
+ * the view group a chance to intercept the hover event.
+ * This method can also be used to watch all pointer motions that occur within
+ * the bounds of the view group even when the pointer is hovering over
+ * a child of the view group rather than over the view group itself.
+ * </p><p>
+ * The view group can prevent its children from receiving hover events by
+ * implementing this method and returning <code>true</code> to indicate
+ * that it would like to intercept hover events. The view group must
+ * continuously return <code>true</code> from {@link #onInterceptHoverEvent}
+ * for as long as it wishes to continue intercepting hover events from
+ * its children.
+ * </p><p>
+ * Interception preserves the invariant that at most one view can be
+ * hovered at a time by transferring hover focus from the currently hovered
+ * child to the view group or vice-versa as needed.
+ * </p><p>
+ * If this method returns <code>true</code> and a child is already hovered, then the
+ * child view will first receive a hover exit event and then the view group
+ * itself will receive a hover enter event in {@link #onHoverEvent}.
+ * Likewise, if this method had previously returned <code>true</code> to intercept hover
+ * events and instead returns <code>false</code> while the pointer is hovering
+ * within the bounds of one of a child, then the view group will first receive a
+ * hover exit event in {@link #onHoverEvent} and then the hovered child will
+ * receive a hover enter event.
+ * </p><p>
+ * The default implementation always returns false.
+ * </p>
+ *
+ * @param event The motion event that describes the hover.
+ * @return True if the view group would like to intercept the hover event
+ * and prevent its children from receiving it.
+ */
+ public boolean onInterceptHoverEvent(MotionEvent event) {
return false;
}
@@ -1335,7 +1465,9 @@
return MotionEvent.obtainNoHistory(event);
}
- /** @hide */
+ /**
+ * {@inheritDoc}
+ */
@Override
protected boolean dispatchGenericPointerEvent(MotionEvent event) {
// Send the event to the child under the pointer.
@@ -1362,7 +1494,9 @@
return super.dispatchGenericPointerEvent(event);
}
- /** @hide */
+ /**
+ * {@inheritDoc}
+ */
@Override
protected boolean dispatchGenericFocusedEvent(MotionEvent event) {
// Send the event to the focused child or to this view group if it has focus.
@@ -3337,10 +3471,6 @@
mTransition.removeChild(this, view);
}
- if (view == mHoveredChild) {
- mHoveredChild = null;
- }
-
boolean clearChildFocus = false;
if (view == mFocused) {
view.clearFocusForRemoval();
@@ -3404,7 +3534,6 @@
final OnHierarchyChangeListener onHierarchyChangeListener = mOnHierarchyChangeListener;
final boolean notifyListener = onHierarchyChangeListener != null;
final View focused = mFocused;
- final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3418,10 +3547,6 @@
mTransition.removeChild(this, view);
}
- if (view == hoveredChild) {
- mHoveredChild = null;
- }
-
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3479,7 +3604,6 @@
final OnHierarchyChangeListener listener = mOnHierarchyChangeListener;
final boolean notify = listener != null;
final View focused = mFocused;
- final View hoveredChild = mHoveredChild;
final boolean detach = mAttachInfo != null;
View clearChildFocus = null;
@@ -3492,10 +3616,6 @@
mTransition.removeChild(this, view);
}
- if (view == hoveredChild) {
- mHoveredChild = null;
- }
-
if (view == focused) {
view.clearFocusForRemoval();
clearChildFocus = view;
@@ -3757,8 +3877,10 @@
if (!childMatrix.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
+ //boundingRect.inset(-0.5f, -0.5f);
childMatrix.mapRect(boundingRect);
- dirty.set((int) boundingRect.left, (int) boundingRect.top,
+ dirty.set((int) (boundingRect.left - 0.5f),
+ (int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
@@ -5242,4 +5364,50 @@
}
}
}
+
+ /* Describes a hovered view. */
+ private static final class HoverTarget {
+ private static final int MAX_RECYCLED = 32;
+ private static final Object sRecycleLock = new Object();
+ private static HoverTarget sRecycleBin;
+ private static int sRecycledCount;
+
+ // The hovered child view.
+ public View child;
+
+ // The next target in the target list.
+ public HoverTarget next;
+
+ private HoverTarget() {
+ }
+
+ public static HoverTarget obtain(View child) {
+ final HoverTarget target;
+ synchronized (sRecycleLock) {
+ if (sRecycleBin == null) {
+ target = new HoverTarget();
+ } else {
+ target = sRecycleBin;
+ sRecycleBin = target.next;
+ sRecycledCount--;
+ target.next = null;
+ }
+ }
+ target.child = child;
+ return target;
+ }
+
+ public void recycle() {
+ synchronized (sRecycleLock) {
+ if (sRecycledCount < MAX_RECYCLED) {
+ next = sRecycleBin;
+ sRecycleBin = this;
+ sRecycledCount += 1;
+ } else {
+ next = null;
+ }
+ child = null;
+ }
+ }
+ }
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 18ef38a..555667b 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -252,7 +252,8 @@
/**
* Finds {@link AccessibilityNodeInfo}s by text. The match is case
- * insensitive containment.
+ * insensitive containment. The search is relative to this info i.e.
+ * this info is the root of the traversed tree.
*
* @param text The searched text.
* @return A list of node info.
@@ -260,7 +261,7 @@
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
enforceSealed();
if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
- return null;
+ return Collections.emptyList();
}
try {
return mConnection.findAccessibilityNodeInfosByViewText(text, mAccessibilityWindowId,
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index b5d4933..bb4d192 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -23,6 +23,10 @@
import java.io.InputStream;
class JniUtil {
+
+ static {
+ System.loadLibrary("webcore");
+ }
private static final String LOGTAG = "webkit";
private JniUtil() {} // Utility class, do not instantiate.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index c56e6db..9f632d1 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -5707,8 +5707,8 @@
return false;
}
WebViewCore.CursorData data = cursorDataNoPosition();
- data.mX = viewToContentX((int) event.getX());
- data.mY = viewToContentY((int) event.getY());
+ data.mX = viewToContentX((int) event.getX() + mScrollX);
+ data.mY = viewToContentY((int) event.getY() + mScrollY);
mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data);
return true;
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 06a61bd..3a09e3b 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2358,7 +2358,7 @@
mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
mViewportInitialScale / 100.0f;
} else if (mViewportWidth > 0 && mViewportWidth < webViewWidth &&
- !mWebView.getSettings().getUseFixedViewport()) {
+ !getSettings().getUseFixedViewport()) {
mInitialViewState.mViewScale = mInitialViewState.mTextWrapScale =
(float) webViewWidth / mViewportWidth;
} else {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 0e52869..1570224 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -535,7 +535,7 @@
return result;
}
- private int getDefaultMargin(View c, boolean leading, boolean horizontal) {
+ private int getDefaultMargin(View c, boolean horizontal, boolean leading) {
// In the absence of any other information, calculate a default gap such
// that, in a grid of identical components, the heights and the vertical
// gaps are in the proportion of the golden ratio.
@@ -544,12 +544,12 @@
return (int) (c.getMeasuredHeight() / GOLDEN_RATIO / 2);
}
- private int getDefaultMargin(View c, boolean isAtEdge, boolean leading, boolean horizontal) {
+ private int getDefaultMargin(View c, boolean isAtEdge, boolean horizontal, boolean leading) {
// todo remove DEFAULT_CONTAINER_MARGIN. Use padding? Seek advice on Themes/Styles, etc.
- return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, leading, horizontal);
+ return isAtEdge ? DEFAULT_CONTAINER_MARGIN : getDefaultMargin(c, horizontal, leading);
}
- private int getDefaultMarginValue(View c, LayoutParams p, boolean leading, boolean horizontal) {
+ private int getDefaultMarginValue(View c, LayoutParams p, boolean horizontal, boolean leading) {
if (!mUseDefaultMargins) {
return 0;
}
@@ -558,15 +558,19 @@
Interval span = group.span;
boolean isAtEdge = leading ? (span.min == 0) : (span.max == axis.getCount());
- return getDefaultMargin(c, isAtEdge, leading, horizontal);
+ return getDefaultMargin(c, isAtEdge, horizontal, leading);
}
- private int getMargin(View view, boolean leading, boolean horizontal) {
+ private int getMargin(View view, boolean horizontal, boolean leading) {
LayoutParams lp = getLayoutParams(view);
int margin = horizontal ?
(leading ? lp.leftMargin : lp.rightMargin) :
(leading ? lp.topMargin : lp.bottomMargin);
- return margin == UNDEFINED ? getDefaultMarginValue(view, lp, leading, horizontal) : margin;
+ return margin == UNDEFINED ? getDefaultMarginValue(view, lp, horizontal, leading) : margin;
+ }
+
+ private int getTotalMargin(View child, boolean horizontal) {
+ return getMargin(child, horizontal, true) + getMargin(child, horizontal, false);
}
private static int valueIfDefined(int value, int defaultValue) {
@@ -749,8 +753,8 @@
View c = getChildAt(i);
drawRectangle(canvas,
c.getLeft() - getMargin(c, true, true),
- c.getTop() - getMargin(c, true, false),
- c.getRight() + getMargin(c, false, true),
+ c.getTop() - getMargin(c, false, true),
+ c.getRight() + getMargin(c, true, false),
c.getBottom() + getMargin(c, false, false), paint);
}
}
@@ -794,17 +798,12 @@
return c.getVisibility() == View.GONE;
}
- private void measureChildWithMargins(View child,
- int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
-
+ private void measureChildWithMargins(View child, int widthMeasureSpec, int heightMeasureSpec) {
LayoutParams lp = getLayoutParams(child);
- int hMargins = getMargin(child, true, true) + getMargin(child, false, true);
- int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
- mPaddingLeft + mPaddingRight + hMargins, lp.width);
- int vMargins = getMargin(child, true, false) + getMargin(child, false, false);
- int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
- mPaddingTop + mPaddingBottom + vMargins, lp.height);
-
+ int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ mPaddingLeft + mPaddingRight + getTotalMargin(child, true), lp.width);
+ int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+ mPaddingTop + mPaddingBottom + getTotalMargin(child, false), lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@@ -842,9 +841,7 @@
private int getMeasurementIncludingMargin(View c, boolean horizontal, int measurementType) {
int result = getMeasurement(c, horizontal, measurementType);
if (mAlignmentMode == ALIGN_MARGINS) {
- int leadingMargin = getMargin(c, true, horizontal);
- int trailingMargin = getMargin(c, false, horizontal);
- return result + leadingMargin + trailingMargin;
+ return result + getTotalMargin(c, horizontal);
}
return result;
}
@@ -919,8 +916,8 @@
if (mAlignmentMode == ALIGN_MARGINS) {
int leftMargin = getMargin(c, true, true);
- int topMargin = getMargin(c, true, false);
- int rightMargin = getMargin(c, false, true);
+ int topMargin = getMargin(c, false, true);
+ int rightMargin = getMargin(c, true, false);
int bottomMargin = getMargin(c, false, false);
// Same calculation as getMeasurementIncludingMargin()
@@ -928,8 +925,8 @@
int mHeight = topMargin + pHeight + bottomMargin;
// Alignment offsets: the location of the view relative to its alignment group.
- int a2vx = colBounds.before - hAlign.getAlignmentValue(c, mWidth, type);
- int a2vy = rowBounds.before - vAlign.getAlignmentValue(c, mHeight, type);
+ int a2vx = colBounds.getOffset(c, hAlign, type, mWidth);
+ int a2vy = rowBounds.getOffset(c, vAlign, type, mHeight);
dx = c2ax + a2vx + leftMargin;
dy = c2ay + a2vy + topMargin;
@@ -938,8 +935,8 @@
cellHeight -= topMargin + bottomMargin;
} else {
// Alignment offsets: the location of the view relative to its alignment group.
- int a2vx = colBounds.before - hAlign.getAlignmentValue(c, pWidth, type);
- int a2vy = rowBounds.before - vAlign.getAlignmentValue(c, pHeight, type);
+ int a2vx = colBounds.getOffset(c, hAlign, type, pWidth);
+ int a2vy = rowBounds.getOffset(c, vAlign, type, pHeight);
dx = c2ax + a2vx;
dy = c2ay + a2vy;
@@ -1051,7 +1048,7 @@
Group group = horizontal ? lp.columnGroup : lp.rowGroup;
groups[i] = group;
- bounds[i] = new Bounds();
+ bounds[i] = group.alignment.getBounds();
}
return new PackedMap<Group, Bounds>(groups, bounds);
@@ -1387,7 +1384,7 @@
Group g = horizontal ? lp.columnGroup : lp.rowGroup;
Interval span = g.span;
int index = leading ? span.min : span.max;
- margins[index] = max(margins[index], getMargin(c, leading, horizontal));
+ margins[index] = max(margins[index], getMargin(c, horizontal, leading));
}
}
@@ -1817,7 +1814,8 @@
}
private int getDefaultWeight(int size) {
- return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0;
+ //return (size == MATCH_PARENT) ? DEFAULT_WEIGHT_1 : DEFAULT_WEIGHT_0;
+ return DEFAULT_WEIGHT_0;
}
private void init(Context context, AttributeSet attrs, int defaultGravity) {
@@ -1996,20 +1994,24 @@
reset();
}
- private void reset() {
+ protected void reset() {
before = Integer.MIN_VALUE;
after = Integer.MIN_VALUE;
}
- private void include(int before, int after) {
+ protected void include(int before, int after) {
this.before = max(this.before, before);
this.after = max(this.after, after);
}
- private int size() {
+ protected int size() {
return before + after;
}
+ protected int getOffset(View c, Alignment alignment, int type, int size) {
+ return before - alignment.getAlignmentValue(c, size, type);
+ }
+
@Override
public String toString() {
return "Bounds{" +
@@ -2235,15 +2237,18 @@
* {@link Group#alignment alignment}. Overall placement of the view in the cell
* group is specified by the two alignments which act along each axis independently.
* <p>
+ * The GridLayout class defines the most common alignments used in general layout:
+ * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link
+ * #BASELINE} and {@link #FILL}.
+ */
+ /*
* An Alignment implementation must define {@link #getAlignmentValue(View, int, int)},
* to return the appropriate value for the type of alignment being defined.
* The enclosing algorithms position the children
* so that the locations defined by the alignment values
* are the same for all of the views in a group.
* <p>
- * The GridLayout class defines the most common alignments used in general layout:
- * {@link #TOP}, {@link #LEFT}, {@link #BOTTOM}, {@link #RIGHT}, {@link #CENTER}, {@link
- * #BASELINE} and {@link #FILL}.
+
*/
public static abstract class Alignment {
private static final Alignment GONE = new Alignment() {
@@ -2253,6 +2258,9 @@
}
};
+ /*pp*/ Alignment() {
+ }
+
/**
* Returns an alignment value. In the case of vertical alignments the value
* returned should indicate the distance from the top of the view to the
@@ -2266,7 +2274,7 @@
*
* @return the alignment value
*/
- public abstract int getAlignmentValue(View view, int viewSize, int measurementType);
+ /*pp*/ abstract int getAlignmentValue(View view, int viewSize, int measurementType);
/**
* Returns the size of the view specified by this alignment.
@@ -2283,9 +2291,13 @@
*
* @return the aligned size
*/
- public int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) {
+ /*pp*/ int getSizeInCell(View view, int viewSize, int cellSize, int measurementType) {
return viewSize;
}
+
+ /*pp*/ Bounds getBounds() {
+ return new Bounds();
+ }
}
private static final Alignment LEADING = new Alignment() {
@@ -2349,11 +2361,42 @@
return UNDEFINED;
}
int baseline = view.getBaseline();
- if (baseline == -1) {
- return UNDEFINED;
- } else {
- return baseline;
- }
+ return (baseline == -1) ? UNDEFINED : baseline;
+ }
+
+ @Override
+ public Bounds getBounds() {
+ return new Bounds() {
+ /*
+ In a baseline aligned row in which some components define a baseline
+ and some don't, we need a third variable to properly account for all
+ the sizes. This tracks the maximum size of all the components -
+ including those that don't define a baseline.
+ */
+ private int size;
+
+ @Override
+ protected void reset() {
+ super.reset();
+ size = 0;
+ }
+
+ @Override
+ protected void include(int before, int after) {
+ super.include(before, after);
+ size = max(size, before + after);
+ }
+
+ @Override
+ protected int size() {
+ return max(super.size(), size);
+ }
+
+ @Override
+ protected int getOffset(View c, Alignment alignment, int type, int size) {
+ return max(0, super.getOffset(c, alignment, type, size));
+ }
+ };
}
};
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 82770ad..17512d8 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -18,6 +18,7 @@
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
+import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.SubMenuBuilder;
import android.content.Context;
@@ -32,12 +33,25 @@
* If the IME is visible the popup will not overlap it until it is touched. Touching outside
* of the popup will dismiss it.
*/
-public class PopupMenu implements MenuBuilder.Callback {
+public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
private Context mContext;
private MenuBuilder mMenu;
private View mAnchor;
private MenuPopupHelper mPopup;
private OnMenuItemClickListener mMenuItemClickListener;
+ private OnDismissListener mDismissListener;
+
+ /**
+ * Callback interface used to notify the application that the menu has closed.
+ */
+ public interface OnDismissListener {
+ /**
+ * Called when the associated menu has been dismissed.
+ *
+ * @param menu The PopupMenu that was dismissed.
+ */
+ public void onDismiss(PopupMenu menu);
+ }
/**
* Construct a new PopupMenu.
@@ -53,6 +67,7 @@
mMenu.setCallback(this);
mAnchor = anchor;
mPopup = new MenuPopupHelper(context, mMenu, anchor);
+ mPopup.setCallback(this);
}
/**
@@ -77,6 +92,15 @@
}
/**
+ * Inflate a menu resource into this PopupMenu. This is equivalent to calling
+ * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+ * @param menuRes Menu resource to inflate
+ */
+ public void inflate(int menuRes) {
+ getMenuInflater().inflate(menuRes, mMenu);
+ }
+
+ /**
* Show the menu popup anchored to the view specified during construction.
* @see #dismiss()
*/
@@ -92,11 +116,25 @@
mPopup.dismiss();
}
+ /**
+ * Set a listener that will be notified when the user selects an item from the menu.
+ *
+ * @param listener Listener to notify
+ */
public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
mMenuItemClickListener = listener;
}
/**
+ * Set a listener that will be notified when this menu is dismissed.
+ *
+ * @param listener Listener to notify
+ */
+ public void setOnDismissListener(OnDismissListener listener) {
+ mDismissListener = listener;
+ }
+
+ /**
* @hide
*/
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
@@ -110,12 +148,15 @@
* @hide
*/
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss(this);
+ }
}
/**
* @hide
*/
- public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (!subMenu.hasVisibleItems()) {
return true;
}
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 586ece8..f3bda43 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -603,7 +603,7 @@
final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
// Should we show the close button? It is not shown if there's no focus,
// field is not iconified by default and there is no text in it.
- final boolean showClose = hasText || mIconifiedByDefault || mQueryTextView.hasFocus();
+ final boolean showClose = hasText || mIconifiedByDefault;
mCloseButton.setVisibility(showClose ? VISIBLE : INVISIBLE);
mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
}
@@ -919,17 +919,22 @@
}
private void onCloseClicked() {
- if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
- CharSequence text = mQueryTextView.getText();
- if (TextUtils.isEmpty(text)) {
+ CharSequence text = mQueryTextView.getText();
+ if (TextUtils.isEmpty(text)) {
+ if (mIconifiedByDefault) {
// query field already empty, hide the keyboard and remove focus
clearFocus();
setImeVisibility(false);
- } else {
- mQueryTextView.setText("");
}
+ } else {
+ mQueryTextView.setText("");
+ mQueryTextView.requestFocus();
+ setImeVisibility(true);
+ }
+
+ if (mIconifiedByDefault && (mOnCloseListener == null || !mOnCloseListener.onClose())) {
updateViewsVisibility(mIconifiedByDefault);
- if (mIconifiedByDefault) setImeVisibility(false);
+ setImeVisibility(false);
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 02c2b8f..17aea8b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7658,12 +7658,17 @@
}
@Override
- public void findViewsWithText(ArrayList<View> outViews, CharSequence text) {
+ public void findViewsWithText(ArrayList<View> outViews, CharSequence searched) {
+ if (TextUtils.isEmpty(searched)) {
+ return;
+ }
CharSequence thisText = getText();
if (TextUtils.isEmpty(thisText)) {
return;
}
- if (thisText.toString().toLowerCase().contains(text)) {
+ String searchedLowerCase = searched.toString().toLowerCase();
+ String thisTextLowerCase = thisText.toString().toLowerCase();
+ if (thisTextLowerCase.contains(searchedLowerCase)) {
outViews.add(this);
}
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
index cc09927..27e4191 100644
--- a/core/java/com/android/internal/view/menu/ListMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -184,12 +184,12 @@
private class MenuAdapter extends BaseAdapter {
public int getCount() {
- ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
return items.size() - mItemIndexOffset;
}
public MenuItemImpl getItem(int position) {
- ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
return items.get(position + mItemIndexOffset);
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 8db7e3c..cffbb4e 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -126,6 +126,7 @@
public void onDismiss() {
mPopup = null;
+ mMenu.close();
if (mTreeObserver != null) {
if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
mTreeObserver.removeGlobalOnLayoutListener(this);
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index c475eff..8d75dff 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -648,7 +648,7 @@
}
}
}
-
+
private void initTitle() {
if (mTitleLayout == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -742,8 +742,14 @@
View homeLayout = mExpandedActionView != null ? mExpandedHomeLayout : mHomeLayout;
if (homeLayout.getVisibility() != GONE) {
- homeLayout.measure(
- MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST),
+ final LayoutParams lp = homeLayout.getLayoutParams();
+ int homeWidthSpec;
+ if (lp.width < 0) {
+ homeWidthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
+ } else {
+ homeWidthSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
+ }
+ homeLayout.measure(homeWidthSpec,
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
final int homeWidth = homeLayout.getMeasuredWidth();
availableWidth = Math.max(0, availableWidth - homeWidth);
@@ -1120,6 +1126,34 @@
width += iconLp.leftMargin + mIconView.getMeasuredWidth() + iconLp.rightMargin;
height = Math.max(height,
iconLp.topMargin + mIconView.getMeasuredHeight() + iconLp.bottomMargin);
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ switch (widthMode) {
+ case MeasureSpec.AT_MOST:
+ width = Math.min(width, widthSize);
+ break;
+ case MeasureSpec.EXACTLY:
+ width = widthSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ break;
+ }
+ switch (heightMode) {
+ case MeasureSpec.AT_MOST:
+ height = Math.min(height, heightSize);
+ break;
+ case MeasureSpec.EXACTLY:
+ height = heightSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ break;
+ }
setMeasuredDimension(width, height);
}
@@ -1127,13 +1161,14 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int vCenter = (b - t) / 2;
int width = r - l;
+ int upOffset = 0;
if (mUpView.getVisibility() != GONE) {
final LayoutParams upLp = (LayoutParams) mUpView.getLayoutParams();
final int upHeight = mUpView.getMeasuredHeight();
final int upWidth = mUpView.getMeasuredWidth();
- final int upTop = t + vCenter - upHeight / 2;
- mUpView.layout(l, upTop, l + upWidth, upTop + upHeight);
- final int upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
+ final int upTop = vCenter - upHeight / 2;
+ mUpView.layout(0, upTop, upWidth, upTop + upHeight);
+ upOffset = upLp.leftMargin + upWidth + upLp.rightMargin;
width -= upOffset;
l += upOffset;
}
@@ -1141,8 +1176,8 @@
final int iconHeight = mIconView.getMeasuredHeight();
final int iconWidth = mIconView.getMeasuredWidth();
final int hCenter = (r - l) / 2;
- final int iconLeft = l + iconLp.leftMargin + hCenter - iconWidth / 2;
- final int iconTop = t + iconLp.topMargin + vCenter - iconHeight / 2;
+ final int iconLeft = upOffset + Math.max(iconLp.leftMargin, hCenter - iconWidth / 2);
+ final int iconTop = Math.max(iconLp.topMargin, vCenter - iconHeight / 2);
mIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index cbb110a..97bbe52 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -103,7 +103,8 @@
private boolean mTactileFeedbackEnabled = true;
private boolean mPatternInProgress = false;
- private float mDiameterFactor = 0.5f;
+ private float mDiameterFactor = 0.10f; // TODO: move to attrs
+ private final int mStrokeAlpha = 128;
private float mHitFactor = 0.6f;
private float mSquareWidth;
@@ -133,6 +134,7 @@
private final Matrix mArrowMatrix = new Matrix();
private final Matrix mCircleMatrix = new Matrix();
+
/**
* Represents a cell in the 3 X 3 matrix of the unlock pattern view.
*/
@@ -267,17 +269,17 @@
mPathPaint.setAntiAlias(true);
mPathPaint.setDither(true);
mPathPaint.setColor(Color.WHITE); // TODO this should be from the style
- mPathPaint.setAlpha(128);
+ mPathPaint.setAlpha(mStrokeAlpha);
mPathPaint.setStyle(Paint.Style.STROKE);
mPathPaint.setStrokeJoin(Paint.Join.ROUND);
mPathPaint.setStrokeCap(Paint.Cap.ROUND);
// lot's of bitmaps!
- mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default);
- mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched);
- mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default);
- mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green);
- mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red);
+ mBitmapBtnDefault = getBitmapFor(R.drawable.btn_code_lock_default_holo);
+ mBitmapBtnTouched = getBitmapFor(R.drawable.btn_code_lock_touched_holo);
+ mBitmapCircleDefault = getBitmapFor(R.drawable.indicator_code_lock_point_area_default_holo);
+ mBitmapCircleGreen = getBitmapFor(R.drawable.indicator_code_lock_point_area_green_holo);
+ mBitmapCircleRed = getBitmapFor(R.drawable.indicator_code_lock_point_area_red_holo);
mBitmapArrowGreenUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_green_up);
mBitmapArrowRedUp = getBitmapFor(R.drawable.indicator_code_lock_drag_direction_red_up);
@@ -464,8 +466,7 @@
break;
case MeasureSpec.EXACTLY:
default:
- // use the specified size, if non-zero
- result = specSize != 0 ? specSize : desired;
+ result = specSize;
}
return result;
}
@@ -890,11 +891,48 @@
final Path currentPath = mCurrentPath;
currentPath.rewind();
+ // draw the circles
+ final int paddingTop = mPaddingTop;
+ final int paddingLeft = mPaddingLeft;
+
+ for (int i = 0; i < 3; i++) {
+ float topY = paddingTop + i * squareHeight;
+ //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
+ for (int j = 0; j < 3; j++) {
+ float leftX = paddingLeft + j * squareWidth;
+ drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
+ }
+ }
+
// TODO: the path should be created and cached every time we hit-detect a cell
// only the last segment of the path should be computed here
// draw the path of the pattern (unless the user is in progress, and
// we are in stealth mode)
final boolean drawPath = (!mInStealthMode || mPatternDisplayMode == DisplayMode.Wrong);
+
+ // draw the arrows associated with the path (unless the user is in progress, and
+ // we are in stealth mode)
+ boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
+ mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
+ if (drawPath) {
+ for (int i = 0; i < count - 1; i++) {
+ Cell cell = pattern.get(i);
+ Cell next = pattern.get(i + 1);
+
+ // only draw the part of the pattern stored in
+ // the lookup table (this is only different in the case
+ // of animation).
+ if (!drawLookup[next.row][next.column]) {
+ break;
+ }
+
+ float leftX = paddingLeft + cell.column * squareWidth;
+ float topY = paddingTop + cell.row * squareHeight;
+
+ drawArrow(canvas, leftX, topY, cell, next);
+ }
+ }
+
if (drawPath) {
boolean anyCircles = false;
for (int i = 0; i < count; i++) {
@@ -925,41 +963,6 @@
canvas.drawPath(currentPath, mPathPaint);
}
- // draw the circles
- final int paddingTop = mPaddingTop;
- final int paddingLeft = mPaddingLeft;
-
- for (int i = 0; i < 3; i++) {
- float topY = paddingTop + i * squareHeight;
- //float centerY = mPaddingTop + i * mSquareHeight + (mSquareHeight / 2);
- for (int j = 0; j < 3; j++) {
- float leftX = paddingLeft + j * squareWidth;
- drawCircle(canvas, (int) leftX, (int) topY, drawLookup[i][j]);
- }
- }
-
- // draw the arrows associated with the path (unless the user is in progress, and
- // we are in stealth mode)
- boolean oldFlag = (mPaint.getFlags() & Paint.FILTER_BITMAP_FLAG) != 0;
- mPaint.setFilterBitmap(true); // draw with higher quality since we render with transforms
- if (drawPath) {
- for (int i = 0; i < count - 1; i++) {
- Cell cell = pattern.get(i);
- Cell next = pattern.get(i + 1);
-
- // only draw the part of the pattern stored in
- // the lookup table (this is only different in the case
- // of animation).
- if (!drawLookup[next.row][next.column]) {
- break;
- }
-
- float leftX = paddingLeft + cell.column * squareWidth;
- float topY = paddingTop + cell.row * squareHeight;
-
- drawArrow(canvas, leftX, topY, cell, next);
- }
- }
mPaint.setFilterBitmap(oldFlag); // restore default flag
}
diff --git a/core/jni/android/graphics/ParcelSurfaceTexture.cpp b/core/jni/android/graphics/ParcelSurfaceTexture.cpp
index 40966e1..754485f 100644
--- a/core/jni/android/graphics/ParcelSurfaceTexture.cpp
+++ b/core/jni/android/graphics/ParcelSurfaceTexture.cpp
@@ -17,9 +17,11 @@
#define LOG_TAG "ParcelSurfaceTexture"
#include <gui/SurfaceTextureClient.h>
+#include <surfaceflinger/Surface.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_graphics_SurfaceTexture.h>
+#include <android_runtime/android_view_Surface.h>
#include <utils/Log.h>
@@ -96,7 +98,16 @@
}
}
-static void ParcelSurfaceTexture_init(JNIEnv* env, jobject thiz, jobject jSurfaceTexture)
+static void ParcelSurfaceTexture_initFromSurface(
+ JNIEnv* env, jobject thiz, jobject jSurface)
+{
+ sp<Surface> surface(Surface_getSurface(env, jSurface));
+ sp<ISurfaceTexture> iSurfaceTexture(surface->getSurfaceTexture());
+ ParcelSurfaceTexture_setISurfaceTexture(env, thiz, iSurfaceTexture);
+}
+
+static void ParcelSurfaceTexture_initFromSurfaceTexture(
+ JNIEnv* env, jobject thiz, jobject jSurfaceTexture)
{
sp<ISurfaceTexture> iSurfaceTexture(
SurfaceTexture_getSurfaceTexture(env, jSurfaceTexture));
@@ -131,8 +142,10 @@
static JNINativeMethod gParcelSurfaceTextureMethods[] = {
{"nativeClassInit", "()V", (void*)ParcelSurfaceTexture_classInit },
- {"nativeInit", "(Landroid/graphics/SurfaceTexture;)V",
- (void *)ParcelSurfaceTexture_init },
+ {"nativeInitFromSurface", "(Landroid/view/Surface;)V",
+ (void *)ParcelSurfaceTexture_initFromSurface },
+ {"nativeInitFromSurfaceTexture", "(Landroid/graphics/SurfaceTexture;)V",
+ (void *)ParcelSurfaceTexture_initFromSurfaceTexture },
{ "nativeFinalize", "()V", (void *)ParcelSurfaceTexture_finalize },
{ "nativeWriteToParcel", "(Landroid/os/Parcel;I)V",
(void *)ParcelSurfaceTexture_writeToParcel },
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 0d28cb1..30ef8df 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -155,9 +155,9 @@
}
static void SurfaceTexture_init(JNIEnv* env, jobject thiz, jint texName,
- jobject weakThiz)
+ jobject weakThiz, jboolean allowSynchronous)
{
- sp<SurfaceTexture> surfaceTexture(new SurfaceTexture(texName));
+ sp<SurfaceTexture> surfaceTexture(new SurfaceTexture(texName, allowSynchronous));
if (surfaceTexture == 0) {
jniThrowException(env, OutOfResourcesException,
"Unable to create native SurfaceTexture");
@@ -212,16 +212,23 @@
return surfaceTexture->getTimestamp();
}
+static jint SurfaceTexture_getQueuedCount(JNIEnv* env, jobject thiz)
+{
+ sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+ return surfaceTexture->getQueuedCount();
+}
+
// ----------------------------------------------------------------------------
static JNINativeMethod gSurfaceTextureMethods[] = {
{"nativeClassInit", "()V", (void*)SurfaceTexture_classInit },
- {"nativeInit", "(ILjava/lang/Object;)V", (void*)SurfaceTexture_init },
+ {"nativeInit", "(ILjava/lang/Object;Z)V", (void*)SurfaceTexture_init },
{"nativeFinalize", "()V", (void*)SurfaceTexture_finalize },
{"nativeSetDefaultBufferSize", "(II)V", (void*)SurfaceTexture_setDefaultBufferSize },
{"nativeUpdateTexImage", "()V", (void*)SurfaceTexture_updateTexImage },
{"nativeGetTransformMatrix", "([F)V", (void*)SurfaceTexture_getTransformMatrix },
- {"nativeGetTimestamp", "()J", (void*)SurfaceTexture_getTimestamp }
+ {"nativeGetTimestamp", "()J", (void*)SurfaceTexture_getTimestamp },
+ {"nativeGetQueuedCount", "()I", (void*)SurfaceTexture_getQueuedCount }
};
int register_android_graphics_SurfaceTexture(JNIEnv* env)
diff --git a/core/jni/android_pim_EventRecurrence.cpp b/core/jni/android_pim_EventRecurrence.cpp
index 44e898d..3e11569 100644
--- a/core/jni/android_pim_EventRecurrence.cpp
+++ b/core/jni/android_pim_EventRecurrence.cpp
@@ -147,7 +147,7 @@
*/
static JNINativeMethod METHODS[] = {
/* name, signature, funcPtr */
- { "parse", "(Ljava/lang/String;)V", (void*)EventRecurrence_parse }
+ { "parseNative", "(Ljava/lang/String;)V", (void*)EventRecurrence_parse }
};
static const char*const CLASS_NAME = "android/pim/EventRecurrence";
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index e301e44..537aae6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -608,8 +608,8 @@
}
static Layer* android_view_GLES20Canvas_createTextureLayer(JNIEnv* env, jobject clazz,
- jintArray layerInfo) {
- Layer* layer = LayerRenderer::createTextureLayer();
+ jboolean isOpaque, jintArray layerInfo) {
+ Layer* layer = LayerRenderer::createTextureLayer(isOpaque);
if (layer) {
jint* storage = env->GetIntArrayElements(layerInfo, NULL);
@@ -645,7 +645,7 @@
}
static void android_view_GLES20Canvas_updateTextureLayer(JNIEnv* env, jobject clazz,
- Layer* layer, jint width, jint height, jobject surface) {
+ Layer* layer, jint width, jint height, jboolean isOpaque, jobject surface) {
float transform[16];
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
@@ -653,7 +653,7 @@
surfaceTexture->getTransformMatrix(transform);
GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
- LayerRenderer::updateTextureLayer(layer, width, height, renderTarget, transform);
+ LayerRenderer::updateTextureLayer(layer, width, height, isOpaque, renderTarget, transform);
}
static void android_view_GLES20Canvas_destroyLayer(JNIEnv* env, jobject clazz, Layer* layer) {
@@ -795,8 +795,8 @@
{ "nCreateLayerRenderer", "(I)I", (void*) android_view_GLES20Canvas_createLayerRenderer },
{ "nCreateLayer", "(IIZ[I)I", (void*) android_view_GLES20Canvas_createLayer },
{ "nResizeLayer", "(III[I)V" , (void*) android_view_GLES20Canvas_resizeLayer },
- { "nCreateTextureLayer", "([I)I", (void*) android_view_GLES20Canvas_createTextureLayer },
- { "nUpdateTextureLayer", "(IIILandroid/graphics/SurfaceTexture;)V",
+ { "nCreateTextureLayer", "(Z[I)I", (void*) android_view_GLES20Canvas_createTextureLayer },
+ { "nUpdateTextureLayer", "(IIIZLandroid/graphics/SurfaceTexture;)V",
(void*) android_view_GLES20Canvas_updateTextureLayer },
{ "nDestroyLayer", "(I)V", (void*) android_view_GLES20Canvas_destroyLayer },
{ "nDestroyLayerDeferred", "(I)V", (void*) android_view_GLES20Canvas_destroyLayerDeferred },
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 70c2f7b..0dc9293 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -156,7 +156,7 @@
static sp<Surface> getSurface(JNIEnv* env, jobject clazz)
{
- sp<Surface> result((Surface*)env->GetIntField(clazz, so.surface));
+ sp<Surface> result(Surface_getSurface(env, clazz));
if (result == 0) {
/*
* if this method is called from the WindowManager's process, it means
@@ -189,6 +189,11 @@
return env->IsInstanceOf(obj, surfaceClass);
}
+sp<Surface> Surface_getSurface(JNIEnv* env, jobject clazz) {
+ sp<Surface> surface((Surface*)env->GetIntField(clazz, so.surface));
+ return surface;
+}
+
static void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface)
{
Surface* const p = (Surface*)env->GetIntField(clazz, so.surface);
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png
index 94d27cf..9409890 100644
--- a/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png
+++ b/core/res/res/drawable-hdpi/btn_code_lock_default_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png
index 94d27cf..55acc9a 100644
--- a/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png
+++ b/core/res/res/drawable-hdpi/btn_code_lock_touched_holo.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/ic_notification_ime_default.png b/core/res/res/drawable-hdpi/ic_notification_ime_default.png
new file mode 100644
index 0000000..1a9d88c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/ic_notification_ime_default.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/title_bar_shadow.9.png b/core/res/res/drawable-hdpi/title_bar_shadow.9.png
new file mode 100644
index 0000000..e6dab63
--- /dev/null
+++ b/core/res/res/drawable-hdpi/title_bar_shadow.9.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png b/core/res/res/drawable-large-mdpi/btn_code_lock_default_holo.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png
rename to core/res/res/drawable-large-mdpi/btn_code_lock_default_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png b/core/res/res/drawable-large-mdpi/btn_code_lock_touched_holo.png
similarity index 100%
copy from core/res/res/drawable-xlarge-mdpi/btn_code_lock_default.png
copy to core/res/res/drawable-large-mdpi/btn_code_lock_touched_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_green_up.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_green_up.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_green_up.png
rename to core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_green_up.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_red_up.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_red_up.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/indicator_code_lock_drag_direction_red_up.png
rename to core/res/res/drawable-large-mdpi/indicator_code_lock_drag_direction_red_up.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_default.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_default_holo.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_default.png
rename to core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_default_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_green.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_green_holo.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_green.png
rename to core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_green_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_red.png b/core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_red_holo.png
similarity index 100%
rename from core/res/res/drawable-xlarge-mdpi/indicator_code_lock_point_area_red.png
rename to core/res/res/drawable-large-mdpi/indicator_code_lock_point_area_red_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png
index 7d11275..82fc3b2 100644
--- a/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png
+++ b/core/res/res/drawable-mdpi/btn_code_lock_default_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png
index 7d11275..d1fe1ad 100644
--- a/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png
+++ b/core/res/res/drawable-mdpi/btn_code_lock_touched_holo.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_notification_ime_default.png b/core/res/res/drawable-mdpi/ic_notification_ime_default.png
new file mode 100644
index 0000000..1a9d88c
--- /dev/null
+++ b/core/res/res/drawable-mdpi/ic_notification_ime_default.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/title_bar_shadow.9.png b/core/res/res/drawable-mdpi/title_bar_shadow.9.png
new file mode 100644
index 0000000..dbcefee
--- /dev/null
+++ b/core/res/res/drawable-mdpi/title_bar_shadow.9.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png
new file mode 100644
index 0000000..f607251
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/btn_code_lock_default_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png
new file mode 100644
index 0000000..e057cdd
--- /dev/null
+++ b/core/res/res/drawable-xhdpi/btn_code_lock_touched_holo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_touched.png b/core/res/res/drawable-xlarge-mdpi/btn_code_lock_touched.png
deleted file mode 100644
index 45cc20d..0000000
--- a/core/res/res/drawable-xlarge-mdpi/btn_code_lock_touched.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png
new file mode 100644
index 0000000..9f7a132
--- /dev/null
+++ b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_default.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png
new file mode 100644
index 0000000..9f7a132
--- /dev/null
+++ b/core/res/res/drawable-xlarge-xhdpi/btn_code_lock_touched.png
Binary files differ
diff --git a/core/res/res/layout-large/action_bar_home.xml b/core/res/res/layout-large/action_bar_home.xml
deleted file mode 100644
index 86580bc..0000000
--- a/core/res/res/layout-large/action_bar_home.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?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.
--->
-
-<view xmlns:android="http://schemas.android.com/apk/res/android"
- class="com.android.internal.widget.ActionBarView$HomeView"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:background="?android:attr/selectableItemBackground" >
- <ImageView android:id="@android:id/up"
- android:src="?android:attr/homeAsUpIndicator"
- android:layout_gravity="center_vertical|left"
- android:visibility="gone"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginRight="-12dip" />
- <ImageView android:id="@android:id/home"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingLeft="16dip"
- android:paddingRight="16dip"
- android:paddingTop="4dip"
- android:paddingBottom="4dip"
- android:adjustViewBounds="true"
- android:layout_gravity="center"
- android:scaleType="fitCenter" />
-</view>
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml b/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml
index 9779074..56c832c 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_glogin_unlock.xml
@@ -26,7 +26,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
- android:layout_above="@+id/emergencyCall">
+ android:layout_above="@+id/emergencyCallButton">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -123,7 +123,7 @@
<!-- emergency call button at bottom center -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
index 1783088..6579e76 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_password_landscape.xml
@@ -95,7 +95,7 @@
<!-- emergency call button NOT CURRENTLY USED -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_emergency"
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
index 63241dd..c63b0ff 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_password_portrait.xml
@@ -87,7 +87,7 @@
<!-- emergency call button -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_emergency"
@@ -98,4 +98,4 @@
/>
</LinearLayout>
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml
index b8cbe51..c65dd83 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_landscape.xml
@@ -104,7 +104,7 @@
android:textSize="18sp"
/>
- <Button android:id="@+id/emergencyCall"
+ <Button android:id="@+id/emergencyCallButton"
android:text="@android:string/lockscreen_emergency_call"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
diff --git a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml
index 009148f..d8bea56 100644
--- a/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml
+++ b/core/res/res/layout-sw600dp/keyguard_screen_sim_pin_portrait.xml
@@ -93,7 +93,7 @@
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="6dip"
- android:layout_above="@id/emergencyCall"
+ android:layout_above="@id/emergencyCallButton"
android:background="@android:drawable/divider_horizontal_dark"
/>
@@ -107,7 +107,7 @@
<!-- emergency call button -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@android:drawable/ic_emergency"
diff --git a/core/res/res/layout/action_bar_home.xml b/core/res/res/layout/action_bar_home.xml
index 7f7c55c..9612710 100644
--- a/core/res/res/layout/action_bar_home.xml
+++ b/core/res/res/layout/action_bar_home.xml
@@ -25,14 +25,13 @@
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginRight="-4dip" />
+ android:layout_marginRight="-8dip" />
<ImageView android:id="@android:id/home"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:paddingLeft="8dip"
- android:paddingRight="8dip"
- android:paddingTop="@dimen/action_bar_icon_vertical_padding"
- android:paddingBottom="@dimen/action_bar_icon_vertical_padding"
+ android:layout_marginRight="8dip"
+ android:layout_marginTop="@android:dimen/action_bar_icon_vertical_padding"
+ android:layout_marginBottom="@android:dimen/action_bar_icon_vertical_padding"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:scaleType="fitCenter" />
diff --git a/core/res/res/layout/action_menu_item_layout.xml b/core/res/res/layout/action_menu_item_layout.xml
index 4206dcb..a8f0c22 100644
--- a/core/res/res/layout/action_menu_item_layout.xml
+++ b/core/res/res/layout/action_menu_item_layout.xml
@@ -29,10 +29,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
- android:paddingTop="@dimen/action_bar_icon_vertical_padding"
- android:paddingBottom="@dimen/action_bar_icon_vertical_padding"
- android:paddingLeft="4dip"
- android:paddingRight="4dip"
+ android:layout_marginTop="4dip"
+ android:layout_marginBottom="4dip"
+ android:layout_marginLeft="4dip"
+ android:layout_marginRight="4dip"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@null"
diff --git a/core/res/res/layout/keyguard_screen_glogin_unlock.xml b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
index 8a46546..0e5fe78 100644
--- a/core/res/res/layout/keyguard_screen_glogin_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_glogin_unlock.xml
@@ -26,7 +26,7 @@
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1"
- android:layout_above="@+id/emergencyCall">
+ android:layout_above="@+id/emergencyCallButton">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -114,7 +114,7 @@
<!-- emergency call button at bottom center -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/core/res/res/layout/keyguard_screen_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index aac0853..937d5dc 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
-** Copyright 2008, The Android Open Source Project
+** Copyright 2009, 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.
@@ -17,89 +17,170 @@
*/
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<!-- This is the general lock screen which shows information about the
+ state of the device, as well as instructions on how to get past it
+ depending on the state of the device.-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical">
+ android:orientation="vertical"
+ android:rowCount="10"
+ android:id="@+id/root"
+ android:clipChildren="false">
- <View
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- />
+ <!-- Column 0 -->
+ <Space android:height="20dip"/>
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ <com.android.internal.widget.DigitalClock android:id="@+id/time"
+ android:layout_marginTop="16dip"
+ android:layout_marginBottom="8dip">
- <!-- left side: status -->
- <include layout="@layout/keyguard_screen_status_land"
+ <!-- Because we can't have multi-tone fonts, we render two TextViews, one on
+ top of the other. Hence the redundant layout... -->
+ <TextView android:id="@+id/timeDisplayBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_centerVertical="true"
- android:layout_alignParentLeft="true"/>
-
- <!-- right side: password -->
- <LinearLayout
- android:layout_width="300dip"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:layout_alignParentRight="true"
- android:layout_centerVertical="true">
-
- <!-- Password entry field -->
- <EditText android:id="@+id/passwordEntry"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:singleLine="true"
- android:textStyle="normal"
- android:inputType="textPassword"
- android:gravity="center"
- android:textSize="24sp"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:background="@drawable/lockscreen_password_field_dark"
- android:textColor="#ffffffff"
- />
-
- <!-- Numeric keyboard -->
- <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
- android:layout_width="300dip"
- android:layout_height="400dip"
- android:background="#40000000"
- android:layout_marginTop="5dip"
- android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
- android:visibility="gone"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@color/lockscreen_clock_background"
/>
- </LinearLayout>
- </RelativeLayout>
+ <TextView android:id="@+id/timeDisplayForeground"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="none"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:layout_marginBottom="6dip"
+ android:textColor="@color/lockscreen_clock_foreground"
+ android:layout_alignLeft="@id/timeDisplayBackground"
+ android:layout_alignTop="@id/timeDisplayBackground"
+ />
- <View
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- />
+ </com.android.internal.widget.DigitalClock>
- <!-- Alphanumeric keyboard -->
- <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboardAlpha"
- android:layout_width="match_parent"
+ <TextView
+ android:id="@+id/date"
+ android:layout_below="@id/time"
+ android:layout_marginTop="6dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:layout_gravity="left"
+ />
+
+ <TextView
+ android:id="@+id/alarm_status"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:layout_marginTop="4dip"
+ android:layout_gravity="left"
+ />
+
+ <TextView
+ android:id="@+id/status1"
+ android:layout_marginTop="4dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:layout_gravity="left"
+ />
+
+ <TextView
+ android:id="@+id/status2"
+ android:layout_marginTop="4dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:layout_gravity="left"
+ />
+
+ <TextView
+ android:id="@+id/screenLocked"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:gravity="center"
+ android:layout_marginTop="4dip"
+ android:drawablePadding="4dip"
+ android:layout_gravity="left"
+ />
+
+ <Space android:height="20dip"/>
+
+ <LinearLayout android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/carrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:layout_gravity="bottom|left"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_marginTop="20dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ android:drawablePadding="8dip"
+ android:layout_marginRight="80dip"
+ android:visibility="visible"
+ />
+ </LinearLayout>
+
+ <Space android:height="20dip"/>
+
+ <!-- Column 1 -->
+ <Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" />
+
+ <!-- Column 2 - password entry field and PIN keyboard -->
+ <EditText android:id="@+id/passwordEntry"
android:layout_height="wrap_content"
- android:background="@drawable/password_keyboard_background_holo"
+ android:layout_width="match_parent"
+ android:singleLine="true"
+ android:textStyle="normal"
+ android:inputType="textPassword"
+ android:gravity="center"
+ android:textSize="24sp"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:background="@drawable/lockscreen_password_field_dark"
+ android:textColor="?android:attr/textColorPrimary"
+ />
+
+ <!-- Numeric keyboard -->
+ <com.android.internal.widget.PasswordEntryKeyboardView android:id="@+id/keyboard"
+ android:layout_width="300dip"
+ android:layout_height="300dip"
+ android:background="#40000000"
+ android:layout_marginTop="5dip"
android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
- android:keyTextSize="18dip"
- android:visibility="gone"
+ android:visibility="visible"
/>
- <!-- emergency call button NOT CURRENTLY USED -->
- <Button
- android:id="@+id/emergencyCall"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableLeft="@drawable/ic_emergency"
- android:drawablePadding="8dip"
- android:text="@string/lockscreen_emergency_call"
- android:visibility="gone"
- style="@style/Widget.Button.Transparent"
- />
-
-</LinearLayout>
+</GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_password_portrait.xml b/core/res/res/layout/keyguard_screen_password_portrait.xml
index 7805672b..30747bc 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -24,23 +24,26 @@
<!-- top: status -->
<RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content">
+ android:layout_height="wrap_content"
+ android:layout_marginRight="16dip">
<include layout="@layout/keyguard_screen_status_port"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"/>
+ android:layout_alignParentRight="true"/>
</RelativeLayout>
<!-- emergency call button -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="4dip"
+ android:layout_marginRight="16dip"
+ android:layout_gravity="right"
android:drawableLeft="@drawable/ic_emergency"
android:drawablePadding="8dip"
android:text="@string/lockscreen_emergency_call"
- android:visibility="gone"
style="@style/Widget.Button.Transparent"
/>
@@ -55,6 +58,8 @@
android:inputType="textPassword"
android:gravity="center"
android:textSize="22sp"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
android:background="@drawable/lockscreen_password_field_dark"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#ffffffff"/>
@@ -83,4 +88,17 @@
android:visibility="gone"
/>
-</LinearLayout>
\ No newline at end of file
+ <TextView
+ android:id="@+id/carrier"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="8dip"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ />
+
+</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
index 244afbe..dff2a3f 100644
--- a/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_sim_pin_landscape.xml
@@ -102,7 +102,7 @@
android:textSize="18sp"
/>
- <Button android:id="@+id/emergencyCall"
+ <Button android:id="@+id/emergencyCallButton"
android:text="@android:string/lockscreen_emergency_call"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
diff --git a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
index 009148f..d8bea56 100644
--- a/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_sim_pin_portrait.xml
@@ -93,7 +93,7 @@
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="6dip"
- android:layout_above="@id/emergencyCall"
+ android:layout_above="@id/emergencyCallButton"
android:background="@android:drawable/divider_horizontal_dark"
/>
@@ -107,7 +107,7 @@
<!-- emergency call button -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@android:drawable/ic_emergency"
diff --git a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml
index d58fb23..11a6e12 100644
--- a/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_sim_puk_landscape.xml
@@ -164,7 +164,7 @@
android:textSize="18sp"
/>
- <Button android:id="@+id/emergencyCall"
+ <Button android:id="@+id/emergencyCallButton"
android:text="@android:string/lockscreen_emergency_call"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
diff --git a/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml b/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml
index 5e392ef..e5e0459 100644
--- a/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_sim_puk_portrait.xml
@@ -159,7 +159,7 @@
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_marginTop="6dip"
- android:layout_above="@id/emergencyCall"
+ android:layout_above="@id/emergencyCallButton"
android:background="@android:drawable/divider_horizontal_dark"
/>
@@ -173,7 +173,7 @@
<!-- emergency call button -->
<Button
- android:id="@+id/emergencyCall"
+ android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@android:drawable/ic_emergency"
diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml
index 8a02e1f..60dd9ff 100644
--- a/core/res/res/layout/keyguard_screen_status_land.xml
+++ b/core/res/res/layout/keyguard_screen_status_land.xml
@@ -26,17 +26,6 @@
android:gravity="left"
>
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"
- android:drawablePadding="4dip"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
-
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -52,7 +41,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="40sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/lockscreen_clock_background"
android:layout_marginBottom="6dip"
@@ -63,7 +52,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="40sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/lockscreen_clock_foreground"
android:layout_alignLeft="@id/timeDisplayBackground"
@@ -71,19 +60,6 @@
android:layout_marginBottom="6dip"
/>
- <TextView android:id="@+id/am_pm"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplayBackground"
- android:layout_alignBaseline="@id/timeDisplayBackground"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="18sp"
- android:layout_marginLeft="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/lockscreen_clock_am_pm"
- />
-
</com.android.internal.widget.DigitalClock>
<LinearLayout
@@ -98,7 +74,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"/>
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
<TextView
android:id="@+id/alarm_status"
@@ -106,7 +82,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="30dip"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"/>
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
</LinearLayout>
@@ -117,7 +93,7 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_marginTop="10dip"
android:drawablePadding="4dip"
android:visibility="gone"
@@ -129,7 +105,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
- android:textSize="17sp"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
diff --git a/core/res/res/layout/keyguard_screen_status_port.xml b/core/res/res/layout/keyguard_screen_status_port.xml
index 1e87fb3..e72df5b 100644
--- a/core/res/res/layout/keyguard_screen_status_port.xml
+++ b/core/res/res/layout/keyguard_screen_status_port.xml
@@ -25,23 +25,10 @@
android:layout_height="wrap_content"
android:gravity="left">
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"
- android:drawablePadding="4dip"
- android:layout_marginTop="10dip"
- android:singleLine="true"
- android:ellipsize="marquee"
- />
-
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="8dip"
- android:layout_marginBottom="8dip">
+ android:layout_marginTop="8dip">
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
top of the other. Hence the redundant layout... -->
@@ -50,7 +37,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="60sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/lockscreen_clock_background"
android:layout_marginBottom="6dip"
@@ -61,7 +48,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="60sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/lockscreen_clock_foreground"
android:layout_marginBottom="6dip"
@@ -69,19 +56,6 @@
android:layout_alignTop="@id/timeDisplayBackground"
/>
- <TextView android:id="@+id/am_pm"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplayBackground"
- android:layout_alignBaseline="@id/timeDisplayBackground"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="22sp"
- android:layout_marginLeft="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/lockscreen_clock_am_pm"
- />
-
</com.android.internal.widget.DigitalClock>
<LinearLayout
@@ -89,45 +63,50 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/time"
- android:layout_marginTop="10dip">
+ android:layout_marginTop="16dip"
+ android:layout_gravity="right">
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"/>
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
<TextView
android:id="@+id/alarm_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="30dip"
+ android:layout_marginLeft="16dip"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"/>
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"/>
</LinearLayout>
- <!-- used for status such as the next alarm, and charging status. -->
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="17sp"
- android:layout_marginTop="10dip"
- android:drawablePadding="4dip"
- android:visibility="gone"
- />
-
<TextView
android:id="@+id/status1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="10dip"
- android:textSize="17sp"
+ android:layout_gravity="right"
+ android:layout_marginTop="4dip"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
+ <!-- used for status such as the next alarm, and charging status. -->
+ <TextView
+ android:id="@+id/status2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right"
+ android:layout_alignParentTop="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:layout_marginTop="4dip"
+ android:drawablePadding="4dip"
+ android:visibility="gone"
+ />
+
+
</LinearLayout>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 7ae357a..020bb27 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -28,43 +28,14 @@
android:id="@+id/root"
android:clipChildren="false">
- <TextView
- android:id="@+id/carrier"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentTop="true"
- android:layout_alignParentRight="true"
- android:layout_marginTop="10dip"
- android:layout_marginRight="8dip"
- android:singleLine="true"
- android:ellipsize="marquee"
- android:gravity="right|bottom"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <!-- "emergency calls only" shown when sim is missing or PUKd -->
- <TextView
- android:id="@+id/emergencyCallText"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_alignParentRight="true"
- android:layout_marginTop="0dip"
- android:layout_marginRight="8dip"
- android:text="@string/emergency_calls_only"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/white"
- />
-
<!-- time and date -->
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/carrier"
- android:layout_marginTop="52dip"
- android:layout_marginLeft="20dip"
- android:layout_marginBottom="8dip"
- >
+ android:layout_alignParentRight="true"
+ android:layout_marginRight="16dip"
+ android:layout_marginTop="48dip"
+ android:layout_marginLeft="20dip">
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
top of the other. Hence the redundant layout... -->
@@ -73,7 +44,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="72sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_background"
@@ -84,7 +55,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="72sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_foreground"
@@ -92,51 +63,50 @@
android:layout_alignTop="@id/timeDisplayBackground"
/>
- <TextView android:id="@+id/am_pm"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplayBackground"
- android:layout_alignBaseline="@id/timeDisplayBackground"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="22sp"
- android:layout_marginLeft="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/lockscreen_clock_am_pm"
- />
-
</com.android.internal.widget.DigitalClock>
- <TextView
- android:id="@+id/date"
- android:layout_width="wrap_content"
+ <LinearLayout android:id="@+id/date_alarm_status_container"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/time"
- android:layout_marginLeft="24dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- />
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_marginTop="16dip"
+ android:layout_alignParentRight="true"
+ android:gravity="right"
+ android:orientation="horizontal">
- <!-- TODO: Redo layout when we release on phones -->
- <TextView
- android:id="@+id/alarm_status"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="18sp"
- android:drawablePadding="4dip"
- android:layout_below="@id/date"
- android:layout_marginTop="4dip"
- android:layout_marginLeft="24dip"
- />
+ <TextView
+ android:id="@+id/date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ />
+
+ <TextView
+ android:id="@+id/alarm_status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:layout_marginLeft="16dip"
+ />
+
+ </LinearLayout>
<TextView
android:id="@+id/status1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/alarm_status"
+ android:layout_below="@id/date_alarm_status_container"
android:layout_marginTop="4dip"
- android:layout_marginLeft="24dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_alignParentRight="true"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
/>
@@ -146,9 +116,13 @@
android:layout_height="wrap_content"
android:layout_below="@id/status1"
android:layout_marginTop="4dip"
- android:layout_marginLeft="24dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_alignParentRight="true"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
+ android:visibility="gone"
/>
<TextView
@@ -156,46 +130,89 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/status2"
- android:layout_marginLeft="24dip"
+ android:layout_marginLeft="16dip"
+ android:layout_marginRight="16dip"
+ android:layout_alignParentRight="true"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_marginTop="12dip"
android:drawablePadding="4dip"
+ android:visibility="gone"
/>
- <com.android.internal.widget.multiwaveview.MultiWaveView
- android:id="@+id/unlock_widget"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="300dip"
- android:layout_alignParentBottom="true"
-
- android:targetDrawables="@array/lockscreen_targets_when_silent"
- android:handleDrawable="@drawable/ic_lockscreen_handle"
- android:waveDrawable="@drawable/ic_lockscreen_outerring"
- android:outerRadius="@*android:dimen/multiwaveview_target_placement_radius"
- android:snapMargin="@*android:dimen/multiwaveview_snap_margin"
- android:hitRadius="@*android:dimen/multiwaveview_hit_radius"
- android:vibrationDuration="20"
- android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right"
- android:feedbackCount="3"
- android:horizontalOffset="0dip"
- android:verticalOffset="60dip"
- />
-
- <!-- emergency call button shown when sim is PUKd and tab_selector is
- hidden -->
+ <!-- emergency call button shown when sim is PUKd and tab_selector is hidden -->
<Button
android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="4dip"
+ android:layout_marginRight="16dip"
android:drawableLeft="@drawable/ic_emergency"
- android:layout_centerInParent="true"
- android:layout_alignParentBottom="true"
- android:layout_marginBottom="80dip"
+ android:layout_alignParentRight="true"
+ android:layout_below="@id/screenLocked"
style="@style/Widget.Button.Transparent"
- android:drawablePadding="8dip"
- android:visibility="gone"
+ android:drawablePadding="4dip"
+ android:text="@string/lockscreen_emergency_call"
+ android:visibility="visible"
+ android:background="@null"
/>
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:gravity="center_horizontal">
+
+ <com.android.internal.widget.multiwaveview.MultiWaveView
+ android:id="@+id/unlock_widget"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="300dip"
+ android:layout_alignParentBottom="true"
+
+ android:targetDrawables="@array/lockscreen_targets_when_silent"
+ android:handleDrawable="@drawable/ic_lockscreen_handle"
+ android:waveDrawable="@drawable/ic_lockscreen_outerring"
+ android:outerRadius="@dimen/multiwaveview_target_placement_radius"
+ android:snapMargin="@dimen/multiwaveview_snap_margin"
+ android:hitRadius="@dimen/multiwaveview_hit_radius"
+ android:rightChevronDrawable="@drawable/ic_lockscreen_chevron_right"
+ android:horizontalOffset="0dip"
+ android:verticalOffset="60dip"
+ android:feedbackCount="3"
+ android:vibrationDuration="20"
+ />
+
+ <TextView
+ android:id="@+id/carrier"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_marginBottom="8dip"
+ android:gravity="center_horizontal"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_above="@id/carrier"
+ android:gravity="center_horizontal"
+ android:layout_marginTop="0dip"
+ android:layout_marginRight="8dip"
+ android:text="@string/emergency_calls_only"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
+
+ </RelativeLayout>
+
</RelativeLayout>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
index d45cc85..930ac33 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -32,8 +32,9 @@
<Space android:height="20dip"/>
<com.android.internal.widget.DigitalClock android:id="@+id/time"
- android:layout_marginTop="56dip"
- android:layout_marginBottom="8dip">
+ android:layout_marginTop="16dip"
+ android:layout_marginBottom="8dip"
+ android:layout_gravity="right">
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
top of the other. Hence the redundant layout... -->
@@ -42,7 +43,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="72sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_background"
@@ -53,7 +54,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="72sp"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_foreground"
@@ -61,20 +62,6 @@
android:layout_alignTop="@id/timeDisplayBackground"
/>
- <TextView android:id="@+id/am_pm"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_toRightOf="@id/timeDisplayBackground"
- android:layout_alignBaseline="@id/timeDisplayBackground"
- android:singleLine="true"
- android:ellipsize="none"
- android:textSize="22sp"
- android:layout_marginLeft="8dip"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/lockscreen_clock_am_pm"
- android:visibility="gone"
- />
-
</com.android.internal.widget.DigitalClock>
<TextView
@@ -82,46 +69,51 @@
android:layout_below="@id/time"
android:layout_marginTop="6dip"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:layout_gravity="left"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/alarm_status"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="18sp"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
android:layout_marginTop="4dip"
- android:layout_gravity="left"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/status1"
android:layout_marginTop="4dip"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
- android:layout_gravity="left"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/status2"
android:layout_marginTop="4dip"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
- android:layout_gravity="left"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/screenLocked"
android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:gravity="center"
android:layout_marginTop="4dip"
android:drawablePadding="4dip"
- android:layout_gravity="left"
+ android:layout_gravity="right"
/>
<Space android:height="20dip"/>
- <LinearLayout android:orientation="horizontal" >
+ <LinearLayout android:orientation="vertical"
+ android:gravity="fill">
<TextView
android:id="@+id/carrier"
@@ -130,19 +122,10 @@
android:layout_alignParentTop="true"
android:singleLine="true"
android:ellipsize="marquee"
- android:layout_gravity="bottom|left"
+ android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium"
- />
-
- <Button
- android:id="@+id/emergencyCallButton"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawableLeft="@drawable/ic_emergency"
- style="@style/Widget.Button.Transparent"
- android:drawablePadding="8dip"
- android:layout_marginRight="80dip"
- android:visibility="gone"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
/>
<!-- "emergency calls only" shown when sim is missing or PUKd -->
@@ -152,14 +135,29 @@
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="20dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:text="@string/emergency_calls_only"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/white"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ android:layout_gravity="right"
+ />
+
+ <Button
+ android:id="@+id/emergencyCallButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawableLeft="@drawable/ic_emergency"
+ android:text="@string/lockscreen_emergency_call"
+ style="@style/Widget.Button.Transparent"
+ android:drawablePadding="8dip"
+ android:layout_marginRight="80dip"
+ android:visibility="visible"
+ android:layout_gravity="right"
/>
</LinearLayout>
- <Space android:height="20dip"/>
-
<!-- Column 1 -->
<Space android:width="20dip" android:layout_columnWeight="1" android:layout_rowSpan="10" />
@@ -173,12 +171,12 @@
android:targetDrawables="@array/lockscreen_targets_when_silent"
android:handleDrawable="@drawable/ic_lockscreen_handle"
android:waveDrawable="@drawable/ic_lockscreen_outerring"
- android:outerRadius="@*android:dimen/multiwaveview_target_placement_radius"
- android:snapMargin="@*android:dimen/multiwaveview_snap_margin"
- android:hitRadius="@*android:dimen/multiwaveview_hit_radius"
- android:vibrationDuration="20"
+ android:outerRadius="@dimen/multiwaveview_target_placement_radius"
+ android:snapMargin="@dimen/multiwaveview_snap_margin"
+ android:hitRadius="@dimen/multiwaveview_hit_radius"
android:topChevronDrawable="@drawable/ic_lockscreen_chevron_up"
android:feedbackCount="3"
+ android:vibrationDuration="20"
android:horizontalOffset="0dip"
android:verticalOffset="0dip"
/>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index d52bc57..8319b8a 100644
--- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
@@ -26,7 +26,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:rowCount="9">
+ android:rowCount="8">
<!-- Column 0: Time, date and status -->
<com.android.internal.widget.DigitalClock android:id="@+id/time"
@@ -44,7 +44,7 @@
android:singleLine="true"
android:ellipsize="none"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_clock_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_background"
/>
@@ -55,7 +55,7 @@
android:singleLine="true"
android:ellipsize="none"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_clock_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:layout_marginBottom="6dip"
android:layout_alignLeft="@id/timeDisplayBackground"
android:layout_alignTop="@id/timeDisplayBackground"
@@ -69,7 +69,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
/>
@@ -78,7 +78,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
android:drawablePadding="4dip"
/>
@@ -88,47 +88,42 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
/>
- <TextView
- android:id="@+id/status2"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
- android:layout_gravity="right"
- android:drawablePadding="4dip"
- android:visibility="gone"
- />
-
- <Space android:layout_rowWeight="1" android:layout_columnWeight="1" />
+ <!-- TODO: remove hard coded height since layout_rowWeight doesn't seem to be working -->
+ <Space
+ android:layout_height="43dip"
+ android:layout_gravity="fill"
+ android:layout_rowWeight="1" android:layout_columnWeight="1" />
<TextView android:id="@+id/carrier"
android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:singleLine="true"
android:ellipsize="marquee"
/>
<Button android:id="@+id/emergencyCallButton"
android:layout_gravity="right"
- style="@*android:style/Widget.Button.Transparent"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ style="@style/Widget.Button.Transparent"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:text="@string/lockscreen_emergency_call"
- android:drawableLeft="@*android:drawable/lockscreen_emergency_button"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
android:drawablePadding="0dip"
+ android:background="@null"
/>
<Button android:id="@+id/forgotPatternButton"
android:layout_gravity="right"
- style="@*android:style/Widget.Button.Transparent"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
- android:text="@*android:string/lockscreen_forgot_pattern_button_text"
- android:drawableLeft="@*android:drawable/lockscreen_forgot_password_button"
+ style="@style/Widget.Button.Transparent"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:text="@string/lockscreen_forgot_pattern_button_text"
+ android:drawableLeft="@drawable/lockscreen_forgot_password_button"
android:drawablePadding="0dip"
+ android:background="@null"
/>
<!-- Column 1: lock pattern -->
@@ -139,6 +134,6 @@
android:layout_marginRight="8dip"
android:layout_marginBottom="8dip"
android:layout_marginLeft="8dip"
- android:layout_rowSpan="9"/>
+ android:layout_rowSpan="8"/>
</GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index dd68d82..bbe2006 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -30,27 +30,28 @@
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_marginBottom="18dip"
+ android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
android:layout_gravity="right">
<!-- Because we can't have multi-tone fonts, we render two TextViews, one on
top of the other. Hence the redundant layout... -->
- <TextView android:id="@*android:id/timeDisplayBackground"
+ <TextView android:id="@+id/timeDisplayBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_clock_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
- android:textColor="@*android:color/lockscreen_clock_background"
+ android:textColor="@color/lockscreen_clock_background"
/>
- <TextView android:id="@*android:id/timeDisplayForeground"
+ <TextView android:id="@+id/timeDisplayForeground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="none"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_clock_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_clock_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginBottom="6dip"
android:textColor="@color/lockscreen_clock_foreground"
@@ -60,14 +61,15 @@
<LinearLayout
android:orientation="horizontal"
- android:layout_gravity="right">
+ android:layout_gravity="right"
+ android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin">
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
/>
<TextView
@@ -76,7 +78,7 @@
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
/>
@@ -85,22 +87,31 @@
<TextView
android:id="@+id/status1"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size"
- android:drawablePadding="4dip"
android:layout_gravity="right"
+ android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
/>
<TextView
android:id="@+id/status2"
- android:layout_alignParentTop="true"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size"
- android:drawablePadding="4dip"
android:layout_gravity="right"
+ android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:drawablePadding="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:visibility="gone"
/>
+ <!-- We need MATCH_PARENT here only to force the size of the parent to be passed to
+ the pattern view for it to compute its size. This is an unusual case, caused by
+ LockPatternView's requirement to maintain a square aspect ratio based on the width
+ of the screen. -->
<com.android.internal.widget.LockPatternView
android:id="@+id/lockPattern"
android:layout_width="match_parent"
@@ -109,6 +120,8 @@
android:layout_marginRight="8dip"
android:layout_marginBottom="4dip"
android:layout_marginLeft="8dip"
+ android:layout_gravity="center|bottom"
+ android:layout_rowWeight="1"
/>
<TextView
@@ -116,25 +129,25 @@
android:layout_gravity="center_horizontal"
android:singleLine="true"
android:ellipsize="marquee"
- android:textSize="@*android:dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
<!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button -->
<LinearLayout
android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_gravity="center">
+ android:layout_gravity="fill_horizontal">
<Button android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
style="@style/Widget.Button.Transparent"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:text="@string/lockscreen_emergency_call"
android:drawableLeft="@drawable/lockscreen_emergency_button"
android:drawablePadding="0dip"
+ android:background="@null"
/>
<Button android:id="@+id/forgotPatternButton"
@@ -142,10 +155,11 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
style="@style/Widget.Button.Transparent"
- android:textSize="@dimen/keyguard_pattern_unlock_status_line_font_size"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:text="@string/lockscreen_forgot_pattern_button_text"
android:drawableLeft="@drawable/lockscreen_forgot_password_button"
android:drawablePadding="0dip"
+ android:background="@null"
/>
</LinearLayout>
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index 7918a3f..790ac6b 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -23,36 +23,58 @@
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
+ android:orientation="vertical"
+ android:background="@color/transparent"
android:focusable="true"
- android:background="?android:attr/actionModeBackground"
android:descendantFocusability="afterDescendants">
- <RelativeLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- >
+ android:background="?android:attr/actionModeBackground"
+ android:orientation="horizontal">
- <ImageView
- android:id="@+id/search_app_icon"
- android:layout_height="48dip"
- android:layout_width="48dip"
- android:layout_marginLeft="8dip"
- android:layout_marginRight="8dip"
- android:layout_gravity="center_vertical"
- android:layout_alignParentLeft="true"
- />
- <SearchView
- android:id="@+id/search_view"
- android:layout_width="match_parent"
+ <!-- Grouped to allow tapping on either item to exit search mode -->
+ <LinearLayout
+ android:id="@id/closeButton"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="600dip"
- android:iconifiedByDefault="false"
- android:layout_alignParentRight="true"
- android:layout_gravity="center_vertical|right"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:src="?android:attr/homeAsUpIndicator"/>
+
+ <ImageView
+ android:id="@+id/search_app_icon"
+ android:layout_height="48dip"
+ android:layout_width="48dip"
+ android:layout_gravity="center_vertical"
/>
- </RelativeLayout>
+ </LinearLayout>
+ <!-- Actual search view with search icon, text field, close
+ and voice buttons -->
+ <SearchView
+ android:id="@+id/search_view"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:maxWidth="600dip"
+ android:iconifiedByDefault="false"
+ android:layout_gravity="center_vertical"
+ />
+
+ </LinearLayout>
+
+ <ImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scaleType="fitXY"
+ android:src="@drawable/title_bar_shadow"/>
</view>
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
index 99fdf5b..475aa59 100644
--- a/core/res/res/layout/search_view.xml
+++ b/core/res/res/layout/search_view.xml
@@ -74,7 +74,6 @@
android:id="@+id/search_app_icon"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_marginRight="7dip"
android:layout_gravity="center_vertical"
android:src="?android:attr/searchViewSearchIcon"
/>
diff --git a/core/res/res/values-land/dimens.xml b/core/res/res/values-land/dimens.xml
index b8ce9b4..c500fd3 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -31,8 +31,14 @@
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height">40dip</dimen>
+ <!-- Vertical padding around action bar icons. -->
+ <dimen name="action_bar_icon_vertical_padding">4dip</dimen>
+ <!-- Text size for action bar titles -->
+ <dimen name="action_bar_title_text_size">16dp</dimen>
+ <!-- Text size for action bar subtitles -->
+ <dimen name="action_bar_subtitle_text_size">12dp</dimen>
- <!-- Size of clock font in LockScreen. -->
- <dimen name="keyguard_pattern_unlock_clock_font_size">80sp</dimen>
+ <!-- Size of clock font in LockScreen on Unsecure unlock screen. -->
+ <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
</resources>
diff --git a/core/res/res/values-large/dimens.xml b/core/res/res/values-large/dimens.xml
index 55eb145..4f49135 100644
--- a/core/res/res/values-large/dimens.xml
+++ b/core/res/res/values-large/dimens.xml
@@ -43,10 +43,4 @@
<!-- Preference UI dimensions for larger screens. -->
<dimen name="preference_widget_width">56dp</dimen>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="max_action_buttons">5</integer>
- <!-- Default height of an action bar. -->
- <dimen name="action_bar_default_height">56dip</dimen>
</resources>
diff --git a/core/res/res/values-sw600dp/bools.xml b/core/res/res/values-sw600dp/bools.xml
index 734031f..d73ff99 100644
--- a/core/res/res/values-sw600dp/bools.xml
+++ b/core/res/res/values-sw600dp/bools.xml
@@ -16,4 +16,5 @@
<resources>
<bool name="preferences_prefer_dual_pane">true</bool>
+ <bool name="show_ongoing_ime_switcher">false</bool>
</resources>
diff --git a/core/res/res/values-sw600dp/dimens.xml b/core/res/res/values-sw600dp/dimens.xml
index 150b6d4..9ffe6b1 100644
--- a/core/res/res/values-sw600dp/dimens.xml
+++ b/core/res/res/values-sw600dp/dimens.xml
@@ -25,6 +25,19 @@
<!-- Size of the giant number (unread count) in the notifications -->
<dimen name="status_bar_content_number_size">48sp</dimen>
+ <!-- The maximum number of action buttons that should be permitted within
+ an action bar/action mode. This will be used to determine how many
+ showAsAction="ifRoom" items can fit. "always" items can override this. -->
+ <integer name="max_action_buttons">5</integer>
+ <!-- Default height of an action bar. -->
+ <dimen name="action_bar_default_height">56dip</dimen>
+ <!-- Vertical padding around action bar icons. -->
+ <dimen name="action_bar_icon_vertical_padding">4dip</dimen>
+ <!-- Text size for action bar titles -->
+ <dimen name="action_bar_title_text_size">18dp</dimen>
+ <!-- Text size for action bar subtitles -->
+ <dimen name="action_bar_subtitle_text_size">14dp</dimen>
+
<!-- Size of clock font in LockScreen. -->
<dimen name="keyguard_pattern_unlock_clock_font_size">98sp</dimen>
diff --git a/core/res/res/values/bools.xml b/core/res/res/values/bools.xml
index 9d6309d..9647bb7 100644
--- a/core/res/res/values/bools.xml
+++ b/core/res/res/values/bools.xml
@@ -19,4 +19,5 @@
<bool name="action_bar_embed_tabs">false</bool>
<bool name="split_action_bar_is_narrow">true</bool>
<bool name="preferences_prefer_dual_pane">false</bool>
+ <bool name="show_ongoing_ime_switcher">true</bool>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 52b00c6..a8b7b75 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -98,7 +98,7 @@
<!-- This string array should be overridden by the device to present a list of network
attributes. This is used by the connectivity manager to decide which networks can coexist
based on the hardware -->
- <!-- An Array of "[Connection name],[ConnectivityManager connection type],
+ <!-- An Array of "[Connection name],[ConnectivityManager.TYPE_xxxx],
[associated radio-type],[priority],[restoral-timer(ms)],[dependencyMet] -->
<!-- the 5th element "resore-time" indicates the number of milliseconds to delay
before automatically restore the default connection. Set -1 if the connection
@@ -154,20 +154,16 @@
<string-array translatable="false" name="config_tether_dhcp_range">
</string-array>
- <!-- Regex array of allowable upstream ifaces for tethering - for example if you want
- tethering on a new interface called "foo2" add <item>"foo\\d"</item> to the array -->
- <!-- Interfaces will be prioritized according to the order listed -->
- <string-array translatable="false" name="config_tether_upstream_regexs">
- </string-array>
-
<!-- Regex of wired ethernet ifaces -->
<string translatable="false" name="config_ethernet_iface_regex">eth\\d</string>
- <!-- Boolean indicating if we require the use of DUN on mobile for tethering.
- Note that this defaults to false so that if you move to a carrier that
- hasn't configured anything tethering will still work. If you'd rather
- make the device untetherable on unconfigured devices, set to true -->
- <bool translatable="false" name="config_tether_dun_required">false</bool>
+ <!-- Array of ConnectivityManager.TYPE_xxxx values allowable for tethering -->
+ <!-- Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+ <!== [0,1,5,7] for TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI and TYPE_BLUETOOTH -->
+ <integer-array translatable="false" name="config_tether_upstream_types">
+ <item>1</item>
+ <item>4</item>
+ </integer-array>
<!-- String containing the apn value for tethering. May be overriden by secure settings
TETHER_DUN_APN. Value is a comma separated series of strings:
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 20cb852..a5e5f70 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -118,15 +118,19 @@
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height">48dip</dimen>
<!-- Vertical padding around action bar icons. -->
- <dimen name="action_bar_icon_vertical_padding">4dip</dimen>
+ <dimen name="action_bar_icon_vertical_padding">8dip</dimen>
+ <!-- Text size for action bar titles -->
+ <dimen name="action_bar_title_text_size">18dp</dimen>
+ <!-- Text size for action bar subtitles -->
+ <dimen name="action_bar_subtitle_text_size">14dp</dimen>
- <!-- Size of clock font in LockScreen. -->
- <dimen name="keyguard_pattern_unlock_clock_font_size">80sp</dimen>
+ <!-- Size of clock font in LockScreen on Unsecure unlock screen. -->
+ <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
- <!-- Size of status line font in LockScreen. -->
- <dimen name="keyguard_pattern_unlock_status_line_font_size">14sp</dimen>
+ <!-- Size of status line font on Unsecure unlock LockScreen. -->
+ <dimen name="keyguard_lockscreen_status_line_font_size">14sp</dimen>
- <!-- Size of right margin in LockScreen -->
- <dimen name="keyguard_pattern_unlock_status_line_font_right_margin">20dip</dimen>
+ <!-- Size of right margin on Unsecure unlock LockScreen -->
+ <dimen name="keyguard_lockscreen_status_line_font_right_margin">45dip</dimen>
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 72a5797..5244b74 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1118,9 +1118,9 @@
<style name="Widget.ActionButton">
<item name="android:background">?android:attr/selectableItemBackground</item>
- <item name="android:paddingLeft">16dip</item>
- <item name="android:paddingRight">16dip</item>
- <item name="android:minWidth">64dip</item>
+ <item name="android:paddingLeft">12dip</item>
+ <item name="android:paddingRight">12dip</item>
+ <item name="android:minWidth">56dip</item>
<item name="android:minHeight">?android:attr/actionBarSize</item>
</style>
@@ -1296,10 +1296,12 @@
<style name="TextAppearance.Holo.Widget.ActionBar.Title"
parent="TextAppearance.Holo.Medium">
+ <item name="android:textSize">@android:dimen/action_bar_title_text_size</item>
</style>
<style name="TextAppearance.Holo.Widget.ActionBar.Subtitle"
parent="TextAppearance.Holo.Small">
+ <item name="android:textSize">@android:dimen/action_bar_subtitle_text_size</item>
</style>
<style name="TextAppearance.Holo.Widget.ActionMode">
@@ -1788,18 +1790,16 @@
</style>
<style name="Widget.Holo.ActionButton" parent="Widget.ActionButton">
- <item name="android:minWidth">64dip</item>
+ <item name="android:minWidth">56dip</item>
<item name="android:gravity">center</item>
- <item name="android:paddingLeft">16dip</item>
- <item name="android:paddingRight">16dip</item>
+ <item name="android:paddingLeft">12dip</item>
+ <item name="android:paddingRight">12dip</item>
<item name="android:scaleType">center</item>
</style>
<style name="Widget.Holo.ActionButton.Overflow">
<item name="android:src">@android:drawable/ic_menu_moreoverflow_holo_dark</item>
<item name="android:background">?android:attr/selectableItemBackground</item>
- <item name="android:paddingLeft">16dip</item>
- <item name="android:paddingRight">16dip</item>
<item name="android:contentDescription">@string/action_menu_overflow_description</item>
</style>
@@ -2142,8 +2142,6 @@
<style name="Widget.Holo.Light.ActionButton.Overflow">
<item name="android:src">@android:drawable/ic_menu_moreoverflow_holo_light</item>
- <item name="android:paddingLeft">16dip</item>
- <item name="android:paddingRight">16dip</item>
<item name="android:contentDescription">@string/action_menu_overflow_description</item>
</style>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 2ab2c04..1fe4804 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -693,17 +693,14 @@
<!-- Theme for the search input bar. -->
<style name="Theme.SearchBar" parent="Theme.Holo.Light.Panel">
- <item name="windowContentOverlay">@null</item>
<item name="actionModeBackground">@android:drawable/cab_background_opaque_holo_light</item>
</style>
<style name="Theme.Holo.SearchBar" parent="Theme.Holo.Panel">
- <item name="windowContentOverlay">@null</item>
<item name="actionModeBackground">@android:drawable/cab_background_opaque_holo_dark</item>
</style>
<style name="Theme.Holo.Light.SearchBar" parent="Theme.Holo.Light.Panel">
- <item name="windowContentOverlay">@null</item>
<item name="actionModeBackground">@android:drawable/cab_background_opaque_holo_light</item>
</style>
diff --git a/core/tests/coretests/src/android/pim/EventRecurrenceTest.java b/core/tests/coretests/src/android/pim/EventRecurrenceTest.java
new file mode 100644
index 0000000..05000f1
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/EventRecurrenceTest.java
@@ -0,0 +1,753 @@
+/*
+ * 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.pim;
+
+import android.pim.EventRecurrence.InvalidFormatException;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.test.suitebuilder.annotation.Suppress;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Test android.pim.EventRecurrence.
+ *
+ * adb shell am instrument -w -e class android.pim.EventRecurrenceTest \
+ * com.android.frameworks.coretests/android.test.InstrumentationTestRunner
+ */
+public class EventRecurrenceTest extends TestCase {
+
+ @SmallTest
+ public void test0() throws Exception {
+ verifyRecurType("FREQ=SECONDLY",
+ /* int freq */ EventRecurrence.SECONDLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test1() throws Exception {
+ verifyRecurType("FREQ=MINUTELY",
+ /* int freq */ EventRecurrence.MINUTELY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test2() throws Exception {
+ verifyRecurType("FREQ=HOURLY",
+ /* int freq */ EventRecurrence.HOURLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test3() throws Exception {
+ verifyRecurType("FREQ=DAILY",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test4() throws Exception {
+ verifyRecurType("FREQ=WEEKLY",
+ /* int freq */ EventRecurrence.WEEKLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test5() throws Exception {
+ verifyRecurType("FREQ=MONTHLY",
+ /* int freq */ EventRecurrence.MONTHLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test6() throws Exception {
+ verifyRecurType("FREQ=YEARLY",
+ /* int freq */ EventRecurrence.YEARLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test7() throws Exception {
+ // with an until
+ verifyRecurType("FREQ=DAILY;UNTIL=112233T223344Z",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ "112233T223344Z",
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test8() throws Exception {
+ // with a count
+ verifyRecurType("FREQ=DAILY;COUNT=334",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 334,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test9() throws Exception {
+ // with a count
+ verifyRecurType("FREQ=DAILY;INTERVAL=5000",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 5000,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @SmallTest
+ public void test10() throws Exception {
+ // verifyRecurType all of the BY* ones with one element
+ verifyRecurType("FREQ=DAILY"
+ + ";BYSECOND=0"
+ + ";BYMINUTE=1"
+ + ";BYHOUR=2"
+ + ";BYMONTHDAY=30"
+ + ";BYYEARDAY=300"
+ + ";BYWEEKNO=53"
+ + ";BYMONTH=12"
+ + ";BYSETPOS=-15"
+ + ";WKST=SU",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ new int[]{0},
+ /* int[] byminute */ new int[]{1},
+ /* int[] byhour */ new int[]{2},
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ new int[]{30},
+ /* int[] byyearday */ new int[]{300},
+ /* int[] byweekno */ new int[]{53},
+ /* int[] bymonth */ new int[]{12},
+ /* int[] bysetpos */ new int[]{-15},
+ /* int wkst */ EventRecurrence.SU
+ );
+ }
+
+ @SmallTest
+ public void test11() throws Exception {
+ // verifyRecurType all of the BY* ones with one element
+ verifyRecurType("FREQ=DAILY"
+ + ";BYSECOND=0,30,59"
+ + ";BYMINUTE=0,41,59"
+ + ";BYHOUR=0,4,23"
+ + ";BYMONTHDAY=-31,-1,1,31"
+ + ";BYYEARDAY=-366,-1,1,366"
+ + ";BYWEEKNO=-53,-1,1,53"
+ + ";BYMONTH=1,12"
+ + ";BYSETPOS=1,2,3,4,500,10000"
+ + ";WKST=SU",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ new int[]{0, 30, 59},
+ /* int[] byminute */ new int[]{0, 41, 59},
+ /* int[] byhour */ new int[]{0, 4, 23},
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ new int[]{-31, -1, 1, 31},
+ /* int[] byyearday */ new int[]{-366, -1, 1, 366},
+ /* int[] byweekno */ new int[]{-53, -1, 1, 53},
+ /* int[] bymonth */ new int[]{1, 12},
+ /* int[] bysetpos */ new int[]{1, 2, 3, 4, 500, 10000},
+ /* int wkst */ EventRecurrence.SU
+ );
+ }
+
+ private static class Check {
+ Check(String k, int... v) {
+ key = k;
+ values = v;
+ }
+
+ String key;
+ int[] values;
+ }
+
+ // this is a negative verifyRecurType case to verifyRecurType the range of the numbers accepted
+ @SmallTest
+ public void test12() throws Exception {
+ Check[] checks = new Check[]{
+ new Check("BYSECOND", -100, -1, 60, 100),
+ new Check("BYMINUTE", -100, -1, 60, 100),
+ new Check("BYHOUR", -100, -1, 24, 100),
+ new Check("BYMONTHDAY", -100, -32, 0, 32, 100),
+ new Check("BYYEARDAY", -400, -367, 0, 367, 400),
+ new Check("BYWEEKNO", -100, -54, 0, 54, 100),
+ new Check("BYMONTH", -100, -5, 0, 13, 100)
+ };
+
+ for (Check ck : checks) {
+ for (int n : ck.values) {
+ String recur = "FREQ=DAILY;" + ck.key + "=" + n;
+ try {
+ EventRecurrence er = new EventRecurrence();
+ er.parse(recur);
+ fail("Negative verifyRecurType failed. "
+ + " parse failed to throw an exception for '"
+ + recur + "'");
+ } catch (EventRecurrence.InvalidFormatException e) {
+ // expected
+ }
+ }
+ }
+ }
+
+ // verifyRecurType BYDAY
+ @SmallTest
+ public void test13() throws Exception {
+ verifyRecurType("FREQ=DAILY;BYDAY=1SU,-2MO,+33TU,WE,TH,FR,SA",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.SU,
+ EventRecurrence.MO,
+ EventRecurrence.TU,
+ EventRecurrence.WE,
+ EventRecurrence.TH,
+ EventRecurrence.FR,
+ EventRecurrence.SA
+ },
+ /* int[] bydayNum */ new int[]{1, -2, 33, 0, 0, 0, 0},
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ @Suppress
+ // Repro bug #2331761 - this should fail because of the last comma into BYDAY
+ public void test14() throws Exception {
+ verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;BYDAY=MO,TU,WE,",
+ /* int freq */ EventRecurrence.WEEKLY,
+ /* String until */ "20100129T130000Z",
+ /* int count */ 0,
+ /* int interval */ 1,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.MO,
+ EventRecurrence.TU,
+ EventRecurrence.WE,
+ },
+ /* int[] bydayNum */ new int[]{0, 0, 0},
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // This test should pass
+ public void test15() throws Exception {
+ verifyRecurType("FREQ=WEEKLY;WKST=MO;UNTIL=20100129T130000Z;INTERVAL=1;"
+ + "BYDAY=MO,TU,WE,TH,FR,SA,SU",
+ /* int freq */ EventRecurrence.WEEKLY,
+ /* String until */ "20100129T130000Z",
+ /* int count */ 0,
+ /* int interval */ 1,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.MO,
+ EventRecurrence.TU,
+ EventRecurrence.WE,
+ EventRecurrence.TH,
+ EventRecurrence.FR,
+ EventRecurrence.SA,
+ EventRecurrence.SU
+ },
+ /* int[] bydayNum */ new int[]{0, 0, 0, 0, 0, 0, 0},
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // Sample coming from RFC2445
+ public void test16() throws Exception {
+ verifyRecurType("FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1",
+ /* int freq */ EventRecurrence.MONTHLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.MO,
+ EventRecurrence.TU,
+ EventRecurrence.WE,
+ EventRecurrence.TH,
+ EventRecurrence.FR
+ },
+ /* int[] bydayNum */ new int[] {0, 0, 0, 0, 0},
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ new int[] { -1 },
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // Sample coming from RFC2445
+ public void test17() throws Exception {
+ verifyRecurType("FREQ=DAILY;COUNT=10;INTERVAL=2",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 10,
+ /* int interval */ 2,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // Sample coming from RFC2445
+ public void test18() throws Exception {
+ verifyRecurType("FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10",
+ /* int freq */ EventRecurrence.YEARLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.SU
+ },
+ /* int[] bydayNum */ new int[] { -1 },
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ new int[] { 10 },
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // Sample coming from bug #1640517
+ public void test19() throws Exception {
+ verifyRecurType("FREQ=YEARLY;BYMONTH=3;BYDAY=TH",
+ /* int freq */ EventRecurrence.YEARLY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ new int[] {
+ EventRecurrence.TH
+ },
+ /* int[] bydayNum */ new int[] { 0 },
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ new int[] { 3 },
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ // for your copying pleasure
+ public void fakeTestXX() throws Exception {
+ verifyRecurType("FREQ=DAILY;",
+ /* int freq */ EventRecurrence.DAILY,
+ /* String until */ null,
+ /* int count */ 0,
+ /* int interval */ 0,
+ /* int[] bysecond */ null,
+ /* int[] byminute */ null,
+ /* int[] byhour */ null,
+ /* int[] byday */ null,
+ /* int[] bydayNum */ null,
+ /* int[] bymonthday */ null,
+ /* int[] byyearday */ null,
+ /* int[] byweekno */ null,
+ /* int[] bymonth */ null,
+ /* int[] bysetpos */ null,
+ /* int wkst */ EventRecurrence.MO
+ );
+ }
+
+ private static void cmp(int vlen, int[] v, int[] correct, String name) {
+ if ((correct == null && v != null)
+ || (correct != null && v == null)) {
+ throw new RuntimeException("One is null, one isn't for " + name
+ + ": correct=" + Arrays.toString(correct)
+ + " actual=" + Arrays.toString(v));
+ }
+ if ((correct == null && vlen != 0)
+ || (vlen != (correct == null ? 0 : correct.length))) {
+ throw new RuntimeException("Reported length mismatch for " + name
+ + ": correct=" + ((correct == null) ? "null" : correct.length)
+ + " actual=" + vlen);
+ }
+ if (correct == null) {
+ return;
+ }
+ if (v.length < correct.length) {
+ throw new RuntimeException("Array length mismatch for " + name
+ + ": correct=" + Arrays.toString(correct)
+ + " actual=" + Arrays.toString(v));
+ }
+ for (int i = 0; i < correct.length; i++) {
+ if (v[i] != correct[i]) {
+ throw new RuntimeException("Array value mismatch for " + name
+ + ": correct=" + Arrays.toString(correct)
+ + " actual=" + Arrays.toString(v));
+ }
+ }
+ }
+
+ private static boolean eq(String a, String b) {
+ if ((a == null && b != null) || (a != null && b == null)) {
+ return false;
+ } else {
+ return a == b || a.equals(b);
+ }
+ }
+
+ private static void verifyRecurType(String recur,
+ int freq, String until, int count, int interval,
+ int[] bysecond, int[] byminute, int[] byhour,
+ int[] byday, int[] bydayNum, int[] bymonthday,
+ int[] byyearday, int[] byweekno, int[] bymonth,
+ int[] bysetpos, int wkst) {
+ EventRecurrence eventRecurrence = new EventRecurrence();
+ eventRecurrence.parse(recur);
+ if (eventRecurrence.freq != freq
+ || !eq(eventRecurrence.until, until)
+ || eventRecurrence.count != count
+ || eventRecurrence.interval != interval
+ || eventRecurrence.wkst != wkst) {
+ System.out.println("Error... got:");
+ print(eventRecurrence);
+ System.out.println("expected:");
+ System.out.println("{");
+ System.out.println(" freq=" + freq);
+ System.out.println(" until=" + until);
+ System.out.println(" count=" + count);
+ System.out.println(" interval=" + interval);
+ System.out.println(" wkst=" + wkst);
+ System.out.println(" bysecond=" + Arrays.toString(bysecond));
+ System.out.println(" byminute=" + Arrays.toString(byminute));
+ System.out.println(" byhour=" + Arrays.toString(byhour));
+ System.out.println(" byday=" + Arrays.toString(byday));
+ System.out.println(" bydayNum=" + Arrays.toString(bydayNum));
+ System.out.println(" bymonthday=" + Arrays.toString(bymonthday));
+ System.out.println(" byyearday=" + Arrays.toString(byyearday));
+ System.out.println(" byweekno=" + Arrays.toString(byweekno));
+ System.out.println(" bymonth=" + Arrays.toString(bymonth));
+ System.out.println(" bysetpos=" + Arrays.toString(bysetpos));
+ System.out.println("}");
+ throw new RuntimeException("Mismatch in fields");
+ }
+ cmp(eventRecurrence.bysecondCount, eventRecurrence.bysecond, bysecond, "bysecond");
+ cmp(eventRecurrence.byminuteCount, eventRecurrence.byminute, byminute, "byminute");
+ cmp(eventRecurrence.byhourCount, eventRecurrence.byhour, byhour, "byhour");
+ cmp(eventRecurrence.bydayCount, eventRecurrence.byday, byday, "byday");
+ cmp(eventRecurrence.bydayCount, eventRecurrence.bydayNum, bydayNum, "bydayNum");
+ cmp(eventRecurrence.bymonthdayCount, eventRecurrence.bymonthday, bymonthday, "bymonthday");
+ cmp(eventRecurrence.byyeardayCount, eventRecurrence.byyearday, byyearday, "byyearday");
+ cmp(eventRecurrence.byweeknoCount, eventRecurrence.byweekno, byweekno, "byweekno");
+ cmp(eventRecurrence.bymonthCount, eventRecurrence.bymonth, bymonth, "bymonth");
+ cmp(eventRecurrence.bysetposCount, eventRecurrence.bysetpos, bysetpos, "bysetpos");
+ }
+
+ private static void print(EventRecurrence er) {
+ System.out.println("{");
+ System.out.println(" freq=" + er.freq);
+ System.out.println(" until=" + er.until);
+ System.out.println(" count=" + er.count);
+ System.out.println(" interval=" + er.interval);
+ System.out.println(" wkst=" + er.wkst);
+ System.out.println(" bysecond=" + Arrays.toString(er.bysecond));
+ System.out.println(" bysecondCount=" + er.bysecondCount);
+ System.out.println(" byminute=" + Arrays.toString(er.byminute));
+ System.out.println(" byminuteCount=" + er.byminuteCount);
+ System.out.println(" byhour=" + Arrays.toString(er.byhour));
+ System.out.println(" byhourCount=" + er.byhourCount);
+ System.out.println(" byday=" + Arrays.toString(er.byday));
+ System.out.println(" bydayNum=" + Arrays.toString(er.bydayNum));
+ System.out.println(" bydayCount=" + er.bydayCount);
+ System.out.println(" bymonthday=" + Arrays.toString(er.bymonthday));
+ System.out.println(" bymonthdayCount=" + er.bymonthdayCount);
+ System.out.println(" byyearday=" + Arrays.toString(er.byyearday));
+ System.out.println(" byyeardayCount=" + er.byyeardayCount);
+ System.out.println(" byweekno=" + Arrays.toString(er.byweekno));
+ System.out.println(" byweeknoCount=" + er.byweeknoCount);
+ System.out.println(" bymonth=" + Arrays.toString(er.bymonth));
+ System.out.println(" bymonthCount=" + er.bymonthCount);
+ System.out.println(" bysetpos=" + Arrays.toString(er.bysetpos));
+ System.out.println(" bysetposCount=" + er.bysetposCount);
+ System.out.println("}");
+ }
+
+
+ /** A list of valid rules. The parser must accept these. */
+ private static final String[] GOOD_RRULES = {
+ /* extracted wholesale from from RFC 2445 section 4.8.5.4 */
+ "FREQ=DAILY;COUNT=10",
+ "FREQ=DAILY;UNTIL=19971224T000000Z",
+ "FREQ=DAILY;INTERVAL=2",
+ "FREQ=DAILY;INTERVAL=10;COUNT=5",
+ "FREQ=YEARLY;UNTIL=20000131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA",
+ "FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1",
+ "FREQ=WEEKLY;COUNT=10",
+ "FREQ=WEEKLY;UNTIL=19971224T000000Z",
+ "FREQ=WEEKLY;INTERVAL=2;WKST=SU",
+ "FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH",
+ "FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH",
+ "FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR",
+ "FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH",
+ "FREQ=MONTHLY;COUNT=10;BYDAY=1FR",
+ "FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR",
+ "FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU",
+ "FREQ=MONTHLY;COUNT=6;BYDAY=-2MO",
+ "FREQ=MONTHLY;BYMONTHDAY=-3",
+ "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15",
+ "FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1",
+ "FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15",
+ "FREQ=MONTHLY;INTERVAL=2;BYDAY=TU",
+ "FREQ=YEARLY;COUNT=10;BYMONTH=6,7",
+ "FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3",
+ "FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200",
+ "FREQ=YEARLY;BYDAY=20MO",
+ "FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO",
+ "FREQ=YEARLY;BYMONTH=3;BYDAY=TH",
+ "FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8",
+ "FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13",
+ "FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13",
+ "FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8",
+ "FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3",
+ "FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2",
+ "FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z",
+ "FREQ=MINUTELY;INTERVAL=15;COUNT=6",
+ "FREQ=MINUTELY;INTERVAL=90;COUNT=4",
+ "FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40",
+ "FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16",
+ "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO",
+ "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU",
+ /* a few more */
+ "FREQ=SECONDLY;BYSECOND=0,15,59",
+ "FREQ=MINUTELY;BYMINUTE=0,15,59",
+ "FREQ=HOURLY;BYHOUR=+0,+15,+23",
+ "FREQ=DAILY;X-WHATEVER=blah", // fails on old parser
+ //"freq=daily;wkst=su", // fails on old parser
+ };
+
+ /** The parser must reject these. */
+ private static final String[] BAD_RRULES = {
+ "INTERVAL=4;FREQ=YEARLY", // FREQ must come first
+ "FREQ=MONTHLY;FREQ=MONTHLY", // can't specify twice
+ "FREQ=MONTHLY;COUNT=1;COUNT=1", // can't specify twice
+ "FREQ=SECONDLY;BYSECOND=60", // range
+ "FREQ=MINUTELY;BYMINUTE=-1", // range
+ "FREQ=HOURLY;BYHOUR=24", // range
+ "FREQ=YEARLY;BYMONTHDAY=0", // zero not valid
+ //"FREQ=YEARLY;COUNT=1;UNTIL=12345", // can't have both COUNT and UNTIL
+ //"FREQ=DAILY;UNTIL=19970829T021400e", // invalid date
+ };
+
+ /**
+ * Simple test of good/bad rules.
+ */
+ @SmallTest
+ public void testBasicParse() {
+ for (String rule : GOOD_RRULES) {
+ EventRecurrence recur = new EventRecurrence();
+ recur.parse(rule);
+ }
+
+ for (String rule : BAD_RRULES) {
+ EventRecurrence recur = new EventRecurrence();
+ boolean didThrow = false;
+
+ try {
+ recur.parse(rule);
+ } catch (InvalidFormatException ife) {
+ didThrow = true;
+ }
+
+ assertTrue("Expected throw on " + rule, didThrow);
+ }
+ }
+}
diff --git a/data/fonts/DroidSansEthiopic-Bold.ttf b/data/fonts/DroidSansEthiopic-Bold.ttf
new file mode 100644
index 0000000..0d4500c
--- /dev/null
+++ b/data/fonts/DroidSansEthiopic-Bold.ttf
Binary files differ
diff --git a/data/fonts/DroidSansEthiopic-Regular.ttf b/data/fonts/DroidSansEthiopic-Regular.ttf
new file mode 100644
index 0000000..dd88aa1
--- /dev/null
+++ b/data/fonts/DroidSansEthiopic-Regular.ttf
Binary files differ
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 55d711f..abea85b 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -270,8 +270,8 @@
</li>
<li><a href="<?cs var:toroot ?>guide/topics/media/index.html">
- <span class="en">Audio and Video</span>
- </a></li>
+ <span class="en">Media</span>
+ </a><span class="new">updated</span></li>
<li>
<a href="<?cs var:toroot ?>guide/topics/clipboard/copy-paste.html">
<span class="en">Copy and Paste</span>
@@ -302,11 +302,13 @@
</li> -->
<!--<li><a style="color:gray;">Localization</a></li> -->
<li><a href="<?cs var:toroot ?>guide/topics/appwidgets/index.html">
- <span class="en">App Widgets</span>
- </a></li>
+ <span class="en">App Widgets</span></a>
+ <span class="new">updated</span>
+ </li>
<li><a href="<?cs var:toroot?>guide/topics/wireless/bluetooth.html">
- <span class="en">Bluetooth</span>
- </a></li>
+ <span class="en">Bluetooth</span></a>
+ <span class="new">updated</span>
+ </li>
<li><a href="<?cs var:toroot?>guide/topics/nfc/index.html">
<span class="en">Near Field Communication</span>
</a></li>
@@ -337,8 +339,8 @@
</ul>
</li>
<li><a href="<?cs var:toroot?>guide/topics/admin/device-admin.html">
- <span class="en">Device Administration</span>
- </a>
+ <span class="en">Device Administration</span></a>
+ <span class="new">updated</span>
</li>
<li class="toggle-list">
<div>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index e589292..78b5b51 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -213,7 +213,7 @@
<p class="note"><strong>Note:</strong> To make your app widget portable across
devices, your app widget's minimum size should never be larger than 4 x 4 cells.
See the <a
-href="{@docRoot}guide/practices/ui_guidelines/widget_design.htmll#sizes">App
+href="{@docRoot}guide/practices/ui_guidelines/widget_design.html#sizes">App
Widget Design Guidelines</a> for more discussion of Home screen cell sizes.</p>
</li>
<li>The <code>updatePeriodMillis</code> attribute defines how often the App
diff --git a/docs/html/guide/topics/fundamentals/activities.jd b/docs/html/guide/topics/fundamentals/activities.jd
index 5cc1b45..cb453da 100644
--- a/docs/html/guide/topics/fundamentals/activities.jd
+++ b/docs/html/guide/topics/fundamentals/activities.jd
@@ -145,7 +145,7 @@
<h3 id="Declaring">Declaring the activity in the manifest</h3>
<p>You must declare your activity in the manifest file in order for it to
-be accessible to the system. To decalare your activity, open your manifest file and add an <a
+be accessible to the system. To declare your activity, open your manifest file and add an <a
href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
as a child of the <a
href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
@@ -163,9 +163,16 @@
<p>There are several other attributes that you can include in this element, to define properties
such as the label for the activity, an icon for the activity, or a theme to style the activity's
-UI. See the <a
+UI. The <a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">{@code android:name}</a>
+attribute is the only required attribute—it specifies the class name of the activity. Once
+you publish your application, you should not change this name, because if you do, you might break
+some functionality, such as application shortcuts (read the blog post, <a
+href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things
+That Cannot Change</a>).</p>
+
+<p>See the <a
href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> element
-reference for more information about available attributes.</p>
+reference for more information about declaring your activity in the manifest.</p>
<h4>Using intent filters</h4>
diff --git a/docs/html/guide/topics/fundamentals/services.jd b/docs/html/guide/topics/fundamentals/services.jd
index 468a417..9c38897 100644
--- a/docs/html/guide/topics/fundamentals/services.jd
+++ b/docs/html/guide/topics/fundamentals/services.jd
@@ -203,7 +203,7 @@
<p>Like activities (and other components), you must declare all services in your application's
manifest file.</p>
-<p>To decalare your service, add a <a
+<p>To declare your service, add a <a
href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
as a child of the <a
href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>
@@ -222,9 +222,17 @@
<p>There are other attributes you can include in the <a
href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element to
define properties such as permissions required to start the service and the process in
-which the service should run. See the <a
+which the service should run. The <a
+href="{@docRoot}guide/topics/manifest/service-element.html#nm">{@code android:name}</a>
+attribute is the only required attribute—it specifies the class name of the service. Once
+you publish your application, you should not change this name, because if you do, you might break
+some functionality where explicit intents are used to reference your service (read the blog post, <a
+href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things
+That Cannot Change</a>).
+
+<p>See the <a
href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> element
-reference for more information.</p>
+reference for more information about declaring your service in the manifest.</p>
<p>Just like an activity, a service can define intent filters that allow other components to
invoke the service using implicit intents. By declaring intent filters, components
@@ -670,7 +678,7 @@
either stopped or removed from the foreground.</p>
<p>For example, a music player that plays music from a service should be set to run in the
-foreground, because the user it explicitly aware
+foreground, because the user is explicitly aware
of its operation. The notification in the status bar might indicate the current song and allow
the user to launch an activity to interact with the music player.</p>
diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd
index c910686..3486212 100644
--- a/docs/html/guide/topics/manifest/activity-element.jd
+++ b/docs/html/guide/topics/manifest/activity-element.jd
@@ -507,6 +507,10 @@
package name specified in the
<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code>
element.
+<p>Once you publish your application, you <a
+href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">should not
+change this name</a> (unless you've set <code><a
+href="#exported">android:exported</a>="false"</code>).</p>
<p>
There is no default. The name must be specified.
diff --git a/docs/html/guide/topics/manifest/manifest-element.jd b/docs/html/guide/topics/manifest/manifest-element.jd
index 598e88f..d737a67 100644
--- a/docs/html/guide/topics/manifest/manifest-element.jd
+++ b/docs/html/guide/topics/manifest/manifest-element.jd
@@ -47,12 +47,15 @@
to "{@code http://schemas.android.com/apk/res/android}".</dd>
<dt><a name="package"></a>{@code package}</dt>
-<dd>A full Java package name for the application. The name should
+<dd>A full Java-language-style package name for the application. The name should
be unique. The name may contain uppercase or lowercase letters ('A'
through 'Z'), numbers, and underscores ('_'). However, individual
-package name parts may only start with letters. For example, applications
-published by Google could have names in the form
-<code>com.google.app.<i>application_name</i></code>.
+package name parts may only start with letters.
+
+<p>To avoid conflicts with other developers, you should use Internet domain ownership as the
+basis for your package names (in reverse). For example, applications published by Google start with
+<code>com.google</code>. You should also never use the <code>com.example</code> namespace when
+publishing your applications.</p>
<p>
The package name serves as a unique identifier for the application.
@@ -66,6 +69,12 @@
element's
<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">taskAffinity</a></code> attribute).
</p>
+
+ <p class="caution"><strong>Caution:</strong> Once you publish your application, you
+<strong>cannot change the package name</strong>. The package name defines your application's
+identity, so if you change it, then it is considered to be a different application and users of
+the previous version cannot update to the new version.</p>
+
</dd>
<dt><a name="uid"></a>{@code android:sharedUserId}</dt>
diff --git a/docs/html/guide/topics/manifest/receiver-element.jd b/docs/html/guide/topics/manifest/receiver-element.jd
index 7012c0f..8416c0c 100644
--- a/docs/html/guide/topics/manifest/receiver-element.jd
+++ b/docs/html/guide/topics/manifest/receiver-element.jd
@@ -122,6 +122,11 @@
"{@code . ReportReceiver}"), it is appended to the package name specified in
the <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> element.
+<p>Once you publish your application, you <a
+href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">should not
+change this name</a> (unless you've set <code><a
+href="#exported">android:exported</a>="false"</code>).</p>
+
<p>
There is no default. The name must be specified.
</p></dd>
diff --git a/docs/html/guide/topics/manifest/service-element.jd b/docs/html/guide/topics/manifest/service-element.jd
index d9a81b3..82d1f6a 100644
--- a/docs/html/guide/topics/manifest/service-element.jd
+++ b/docs/html/guide/topics/manifest/service-element.jd
@@ -6,7 +6,7 @@
<dl class="xml">
<dt>syntax:</dt>
<dd><pre class="stx"><service android:<a href="#enabled">enabled</a>=["true" | "false"]
- android:<a href="#exported">exported[</a>="true" | "false"]
+ android:<a href="#exported">exported</a>=["true" | "false"]
android:<a href="#icon">icon</a>="<i>drawable resource</i>"
android:<a href="#label">label</a>="<i>string resource</i>"
android:<a href="#nm">name</a>="<i>string</i>"
@@ -121,6 +121,11 @@
it is appended to the package name specified in the
<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> element.
+<p>Once you publish your application, you <a
+href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">should not
+change this name</a> (unless you've set <code><a
+href="#exported">android:exported</a>="false"</code>).</p>
+
<p>
There is no default. The name must be specified.
</p></dd>
diff --git a/docs/html/guide/topics/media/images/notification1.png b/docs/html/guide/topics/media/images/notification1.png
new file mode 100644
index 0000000..9b01971
--- /dev/null
+++ b/docs/html/guide/topics/media/images/notification1.png
Binary files differ
diff --git a/docs/html/guide/topics/media/images/notification2.png b/docs/html/guide/topics/media/images/notification2.png
new file mode 100644
index 0000000..488648e
--- /dev/null
+++ b/docs/html/guide/topics/media/images/notification2.png
Binary files differ
diff --git a/docs/html/guide/topics/media/index.jd b/docs/html/guide/topics/media/index.jd
index b6d1629..06e6208 100644
--- a/docs/html/guide/topics/media/index.jd
+++ b/docs/html/guide/topics/media/index.jd
@@ -1,4 +1,4 @@
-page.title=Audio and Video
+page.title=Media
@jd:body
<div id="qv-wrapper">
@@ -6,30 +6,44 @@
<h2>Quickview</h2>
<ul>
-<li>Audio playback and record</li>
-<li>Video playback</li>
-<li>Handles data from raw resources, files, streams</li>
-<li>Built-in codecs for a variety of media. See <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li>
+<li>MediaPlayer APIs allow you to play and record media</li>
+<li>You can handle data from raw resources, files, and streams</li>
+<li>The platform supports a variety of media formats. See <a
+href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media Formats</a></li>
</ul>
<h2>In this document</h2>
<ol>
-<li><a href="#playback">Audio and Video Playback</a>
- <ol>
- <li><a href="#playraw">Playing from a Raw Resource</li>
- <li><a href="#playfile">Playing from a File or Stream</li>
- <li><a href="#jet">Playing JET Content</li>
- </ol>
+<li><a href="#mediaplayer">Using MediaPlayer</a>
+ <ol>
+ <li><a href='#preparingasync'>Asynchronous Preparation</a></li>
+ <li><a href='#managestate'>Managing State</a></li>
+ <li><a href='#releaseplayer'>Releasing the MediaPlayer</a></li>
+ </ol>
</li>
-<li><a href="#capture">Audio Capture</a></li>
+<li><a href="#mpandservices">Using a Service with MediaPlayer</a>
+ <ol>
+ <li><a href="#asyncprepare">Running asynchronously</a></li>
+ <li><a href="#asyncerror">Handling asynchronous errors</a></li>
+ <li><a href="#wakelocks">Using wake locks</a></li>
+ <li><a href="#foregroundserv">Running as a foreground service</a></li>
+ <li><a href="#audiofocus">Handling audio focus</a></li>
+ <li><a href="#cleanup">Performing cleanup</a></li>
+ </ol>
+</li>
+<li><a href="#noisyintent">Handling the AUDIO_BECOMING_NOISY Intent</a>
+<li><a href="#viacontentresolver">Retrieving Media from a Content Resolver</a>
+<li><a href="#jetcontent">Playing JET content</a>
+<li><a href="#audiocapture">Performing Audio Capture</a>
</ol>
<h2>Key classes</h2>
<ol>
-<li>{@link android.media.MediaPlayer MediaPlayer}</li>
-<li>{@link android.media.MediaRecorder MediaRecorder}</li>
-<li>{@link android.media.JetPlayer JetPlayer}</li>
-<li>{@link android.media.SoundPool SoundPool}</li>
+<li>{@link android.media.MediaPlayer}</li>
+<li>{@link android.media.MediaRecorder}</li>
+<li>{@link android.media.AudioManager}</li>
+<li>{@link android.media.JetPlayer}</li>
+<li>{@link android.media.SoundPool}</li>
</ol>
<h2>See also</h2>
@@ -41,129 +55,729 @@
</div>
</div>
-<p>The Android platform offers built-in encoding/decoding for a variety of
-common media types, so that you can easily integrate audio, video, and images into your
-applications. Accessing the platform's media capabilities is fairly straightforward
-— you do so using the same intents and activities mechanism that the rest of
-Android uses.</p>
+<p>The Android multimedia framework includes support for encoding and decoding a
+variety of common media types, so that you can easily integrate audio,
+video and images into your applications. You can play audio or video from media files stored in your
+application's resources (raw resources), from standalone files in the filesystem, or from a data
+stream arriving over a network connection, all using {@link android.media.MediaPlayer} APIs.</p>
-<p>Android lets you play audio and video from several types of data sources. You
-can play audio or video from media files stored in the application's resources
-(raw resources), from standalone files in the filesystem, or from a data stream
-arriving over a network connection. To play audio or video from your
-application, use the {@link android.media.MediaPlayer} class.</p>
+<p>You can also record audio and video using the {@link android.media.MediaRecorder} APIs if
+supported by the device hardware. Note that the emulator doesn't have hardware to capture audio or
+video, but actual mobile devices are likely to provide these capabilities.</p>
-<p>The platform also lets you record audio and video, where supported by the
-mobile device hardware. To record audio or video, use the {@link
-android.media.MediaRecorder} class. Note that the emulator doesn't have hardware
-to capture audio or video, but actual mobile devices are likely to provide these
-capabilities, accessible through the MediaRecorder class. </p>
+<p>This document shows you how to write a media-playing application that interacts with the user and
+the system in order to obtain good performance and a pleasant user experience.</p>
-<p>For a list of media formats for which Android offers built-in support,
-see the <a href="{@docRoot}guide/appendix/media-formats.html">Android Media
-Formats</a> appendix. </p>
+<p class="note"><strong>Note:</strong> You can play back the audio data only to the standard output
+device. Currently, that is the mobile device speaker or a Bluetooth headset. You cannot play sound
+files in the conversation audio during a call.</p>
-<h2 id="playback">Audio and Video Playback</h2>
-<p>Media can be played from anywhere: from a raw resource, from a file from the system,
-or from an available network (URL).</p>
-
-<p>You can play back the audio data only to the standard
-output device; currently, that is the mobile device speaker or Bluetooth headset. You
-cannot play sound files in the conversation audio. </p>
-<h3 id="playraw">Playing from a Raw Resource</h3>
-<p>Perhaps the most common thing to want to do is play back media (notably sound)
-within your own applications. Doing this is easy:</p>
-<ol>
- <li>Put the sound (or other media resource) file into the <code>res/raw</code>
- folder of your project, where the Eclipse plugin (or aapt) will find it and
- make it into a resource that can be referenced from your R class</li>
- <li>Create an instance of <code>MediaPlayer</code>, referencing that resource using
- {@link android.media.MediaPlayer#create MediaPlayer.create}, and then call
- {@link android.media.MediaPlayer#start() start()} on the instance:</li>
-</ol>
-<pre>
- MediaPlayer mp = MediaPlayer.create(context, R.raw.sound_file_1);
- mp.start();
+<h2 id="mediaplayer">Using MediaPlayer</h2>
+
+<p>One of the most important components of the media framework is the
+{@link android.media.MediaPlayer MediaPlayer}
+class. An object of this class can fetch, decode, and play both audio and video
+with minimal setup. It supports several different media sources such as:
+<ul>
+ <li>Local resources</li>
+ <li>Internal URIs, such as one you might obtain from a Content Resolver</li>
+ <li>External URLs (streaming)</li>
+</ul>
+</p>
+
+<p>For a list of media formats that Android supports,
+see the <a href="{@docRoot}guide/appendix/media-formats.html">Android Supported Media
+Formats</a> document. </p>
+
+<p>Here is an example
+of how to play audio that's available as a local raw resource (saved in your application's
+{@code res/raw/} directory):</p>
+
+<pre>MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
+mediaPlayer.start(); // no need to call prepare(); create() does that for you
</pre>
-<p>To stop playback, call {@link android.media.MediaPlayer#stop() stop()}. If
-you wish to later replay the media, then you must
-{@link android.media.MediaPlayer#reset() reset()} and
-{@link android.media.MediaPlayer#prepare() prepare()} the MediaPlayer object
-before calling {@link android.media.MediaPlayer#start() start()} again.
-(<code>create()</code> calls <code>prepare()</code> the first time.)</p>
-<p>To pause playback, call {@link android.media.MediaPlayer#pause() pause()}.
-Resume playback from where you paused with
-{@link android.media.MediaPlayer#start() start()}.</p>
-<h3 id="playfile">Playing from a File or Stream</h3>
-<p>You can play back media files from the filesystem or a web URL:</p>
-<ol>
- <li>Create an instance of the <code>MediaPlayer</code> using <code>new</code></li>
- <li>Call {@link android.media.MediaPlayer#setDataSource setDataSource()}
- with a String containing the path (local filesystem or URL)
- to the file you want to play</li>
- <li>First {@link android.media.MediaPlayer#prepare prepare()} then
- {@link android.media.MediaPlayer#start() start()} on the instance:</li>
-</ol>
-<pre>
- MediaPlayer mp = new MediaPlayer();
- mp.setDataSource(PATH_TO_FILE);
- mp.prepare();
- mp.start();
-</pre>
-<p>{@link android.media.MediaPlayer#stop() stop()} and
-{@link android.media.MediaPlayer#pause() pause()} work the same as discussed
-above.</p>
- <p class="note"><strong>Note:</strong>
- <code>IllegalArgumentException</code> and <code>IOException</code> either
- need to be caught or passed on when using <code>setDataSource()</code>, since
- the file you are referencing may not exist.</p>
+<p>In this case, a "raw" resource is a file that the system does not
+try to parse in any particular way. However, the content of this resource should not
+be raw audio. It should be a properly encoded and formatted media file in one
+of the supported formats.</p>
+
+<p>And here is how you might play from a URI available locally in the system
+(that you obtained through a Content Resolver, for instance):</p>
+
+<pre>Uri myUri = ....; // initialize Uri here
+MediaPlayer mediaPlayer = new MediaPlayer();
+mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+mediaPlayer.setDataSource(getApplicationContext(), myUri);
+mediaPlayer.prepare();
+mediaPlayer.start();</pre>
+
+<p>Playing from a remote URL via HTTP streaming looks like this:</p>
+
+<pre>String url = "http://........"; // your URL here
+MediaPlayer mediaPlayer = new MediaPlayer();
+mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+mediaPlayer.setDataSource(url);
+mediaPlayer.prepare(); // might take long! (for buffering, etc)
+mediaPlayer.start();</pre>
+
<p class="note"><strong>Note:</strong>
-If you're passing a URL to an online media file, the file must be capable of
+If you're passing a URL to stream an online media file, the file must be capable of
progressive download.</p>
-<h3 id="jet">Playing JET content</h3>
-<p>The Android platform includes a JET engine that lets you add interactive playback of JET audio content in your applications. You can create JET content for interactive playback using the JetCreator authoring application that ships with the SDK. To play and manage JET content from your application, use the {@link android.media.JetPlayer JetPlayer} class.</p>
+<p class="caution"><strong>Caution:</strong> You must either catch or pass
+{@link java.lang.IllegalArgumentException} and {@link java.io.IOException} when using
+{@link android.media.MediaPlayer#setDataSource setDataSource()}, because
+the file you are referencing might not exist.</p>
-<p>For a description of JET concepts and instructions on how to use the JetCreator authoring tool, see the <a href="{@docRoot}guide/topics/media/jet/jetcreator_manual.html">JetCreator User Manual</a>. The tool is available fully-featured on the OS X and Windows platforms and the Linux version supports all the content creation features, but not the auditioning of the imported assets. </p>
+<h3 id='#preparingasync'>Asynchronous Preparation</h3>
-<p>Here's an example of how to set up JET playback from a .jet file stored on the SD card:</p>
+<p>Using {@link android.media.MediaPlayer MediaPlayer} can be straightforward in
+principle. However, it's important to keep in mind that a few more things are
+necessary to integrate it correctly with a typical Android application. For
+example, the call to {@link android.media.MediaPlayer#prepare prepare()} can
+take a long time to execute, because
+it might involve fetching and decoding media data. So, as is the case with any
+method that may take long to execute, you should <strong>never call it from your
+application's UI thread</strong>. Doing that will cause the UI to hang until the method returns,
+which is a very bad user experience and can cause an ANR (Application Not Responding) error. Even if
+you expect your resource to load quickly, remember that anything that takes more than a tenth
+of a second to respond in the UI will cause a noticeable pause and will give
+the user the impression that your application is slow.</p>
+
+<p>To avoid hanging your UI thread, spawn another thread to
+prepare the {@link android.media.MediaPlayer} and notify the main thread when done. However, while
+you could write the threading logic
+yourself, this pattern is so common when using {@link android.media.MediaPlayer} that the framework
+supplies a convenient way to accomplish this task by using the
+{@link android.media.MediaPlayer#prepareAsync prepareAsync()} method. This method
+starts preparing the media in the background and returns immediately. When the media
+is done preparing, the {@link android.media.MediaPlayer.OnPreparedListener#onPrepared onPrepared()}
+method of the {@link android.media.MediaPlayer.OnPreparedListener
+MediaPlayer.OnPreparedListener}, configured through
+{@link android.media.MediaPlayer#setOnPreparedListener setOnPreparedListener()} is called.</p>
+
+<h3 id='#managestate'>Managing State</h3>
+
+<p>Another aspect of a {@link android.media.MediaPlayer} that you should keep in mind is
+that it's state-based. That is, the {@link android.media.MediaPlayer} has an internal state
+that you must always be aware of when writing your code, because certain operations
+are only valid when then player is in specific states. If you perform an operation while in the
+wrong state, the system may throw an exception or cause other undesireable behaviors.</p>
+
+<p>The documentation in the
+{@link android.media.MediaPlayer MediaPlayer} class shows a complete state diagram,
+that clarifies which methods move the {@link android.media.MediaPlayer} from one state to another.
+For example, when you create a new {@link android.media.MediaPlayer}, it is in the <em>Idle</em>
+state. At that point, you should initialize it by calling
+{@link android.media.MediaPlayer#setDataSource setDataSource()}, bringing it
+to the <em>Initialized</em> state. After that, you have to prepare it using either the
+{@link android.media.MediaPlayer#prepare prepare()} or
+{@link android.media.MediaPlayer#prepareAsync prepareAsync()} method. When
+the {@link android.media.MediaPlayer} is done preparing, it will then enter the <em>Prepared</em>
+state, which means you can call {@link android.media.MediaPlayer#start start()}
+to make it play the media. At that point, as the diagram illustrates,
+you can move between the <em>Started</em>, <em>Paused</em> and <em>PlaybackCompleted</em> states by
+calling such methods as
+{@link android.media.MediaPlayer#start start()},
+{@link android.media.MediaPlayer#pause pause()}, and
+{@link android.media.MediaPlayer#seekTo seekTo()},
+amongst others. When you
+call {@link android.media.MediaPlayer#stop stop()}, however, notice that you
+cannot call {@link android.media.MediaPlayer#start start()} again until you
+prepare the {@link android.media.MediaPlayer} again.</p>
+
+<p>Always keep <a href='{@docRoot}images/mediaplayer_state_diagram.gif'>the state diagram</a>
+in mind when writing code that interacts with a
+{@link android.media.MediaPlayer} object, because calling its methods from the wrong state is a
+common cause of bugs.</p>
+
+<h3 id='#releaseplayer'>Releasing the MediaPlayer</h3>
+
+<p>A {@link android.media.MediaPlayer MediaPlayer} can consume valuable
+system resources.
+Therefore, you should always take extra precautions to make sure you are not
+hanging on to a {@link android.media.MediaPlayer} instance longer than necessary. When you
+are done with it, you should always call
+{@link android.media.MediaPlayer#release release()} to make sure any
+system resources allocated to it are properly released. For example, if you are
+using a {@link android.media.MediaPlayer} and your activity receives a call to {@link
+android.app.Activity#onStop onStop()}, you must release the {@link android.media.MediaPlayer},
+because it
+makes little sense to hold on to it while your activity is not interacting with
+the user (unless you are playing media in the background, which is discussed in the next section).
+When your activity is resumed or restarted, of course, you need to
+create a new {@link android.media.MediaPlayer} and prepare it again before resuming playback.</p>
+
+<p>Here's how you should release and then nullify your {@link android.media.MediaPlayer}:</p>
+<pre>
+mediaPlayer.release();
+mediaPlayer = null;
+</pre>
+
+<p>As an example, consider the problems that could happen if you
+forgot to release the {@link android.media.MediaPlayer} when your activity is stopped, but create a
+new one when the activity starts again. As you may know, when the user changes the
+screen orientation (or changes the device configuration in another way),
+the system handles that by restarting the activity (by default), so you might quickly
+consume all of the system resources as the user
+rotates the device back and forth between portrait and landscape, because at each
+orientation change, you create a new {@link android.media.MediaPlayer} that you never
+release. (For more information about runtime restarts, see <a
+href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a>.)</p>
+
+<p>You may be wondering what happens if you want to continue playing
+"background media" even when the user leaves your activity, much in the same
+way that the built-in Music application behaves. In this case, what you need is
+a {@link android.media.MediaPlayer MediaPlayer} controlled by a {@link android.app.Service}, as
+discussed in <a href="mpandservices">Using a Service with MediaPlayer</a>.</p>
+
+<h2 id="mpandservices">Using a Service with MediaPlayer</h2>
+
+<p>If you want your media to play in the background even when your application
+is not onscreen—that is, you want it to continue playing while the user is
+interacting with other applications—then you must start a
+{@link android.app.Service Service} and control the
+{@link android.media.MediaPlayer MediaPlayer} instance from there.
+You should be careful about this setup, because the user and the system have expectations
+about how an application running a background service should interact with the rest of the
+system. If your application does not fulfil those expectations, the user may
+have a bad experience. This section describes the main issues that you should be
+aware of and offers suggestions about how to approach them.</p>
+
+
+<h3 id="asyncprepare">Running asynchronously</h3>
+
+<p>First of all, like an {@link android.app.Activity Activity}, all work in a
+{@link android.app.Service Service} is done in a single thread by
+default—in fact, if you're running an activity and a service from the same application, they
+use the same thread (the "main thread") by default. Therefore, services need to
+process incoming intents quickly
+and never perform lengthy computations when responding to them. If any heavy
+work or blocking calls are expected, you must do those tasks asynchronously: either from
+another thread you implement yourself, or using the framework's many facilities
+for asynchronous processing.</p>
+
+<p>For instance, when using a {@link android.media.MediaPlayer} from your main thread,
+you should call {@link android.media.MediaPlayer#prepareAsync prepareAsync()} rather than
+{@link android.media.MediaPlayer#prepare prepare()}, and implement
+a {@link android.media.MediaPlayer.OnPreparedListener MediaPlayer.OnPreparedListener}
+in order to be notified when the preparation is complete and you can start playing.
+For example:</p>
<pre>
-JetPlayer myJet = JetPlayer.getJetPlayer();
-myJet.loadJetFile("/sdcard/level1.jet");
+public class MyService extends Service implements MediaPlayer.OnPreparedListener {
+ private static final ACTION_PLAY = "com.example.action.PLAY";
+ MediaPlayer mMediaPlayer = null;
+
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ ...
+ if (intent.getAction().equals(ACTION_PLAY)) {
+ mMediaPlayer = ... // initialize it here
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.prepareAsync(); // prepare async to not block main thread
+ }
+ }
+
+ /** Called when MediaPlayer is ready */
+ public void onPrepared(MediaPlayer player) {
+ player.start();
+ }
+}
+</pre>
+
+
+<h3 id="asyncerror">Handling asynchronous errors</h3>
+
+<p>On synchronous operations, errors would normally
+be signaled with an exception or an error code, but whenever you use asynchronous
+resources, you should make sure your application is notified
+of errors appropriately. In the case of a {@link android.media.MediaPlayer MediaPlayer},
+you can accomplish this by implementing a
+{@link android.media.MediaPlayer.OnErrorListener MediaPlayer.OnErrorListener} and
+setting it in your {@link android.media.MediaPlayer} instance:</p>
+
+<pre>
+public class MyService extends Service implements MediaPlayer.OnErrorListener {
+ MediaPlayer mMediaPlayer;
+
+ public void initMediaPlayer() {
+ // ...initialize the MediaPlayer here...
+
+ mMediaPlayer.setOnErrorListener(this);
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ // ... react appropriately ...
+ // The MediaPlayer has moved to the Error state, must be reset!
+ }
+}
+</pre>
+
+<p>It's important to remember that when an error occurs, the {@link android.media.MediaPlayer}
+moves to the <em>Error</em> state (see the documentation for the
+{@link android.media.MediaPlayer MediaPlayer} class for the full state diagram)
+and you must reset it before you can use it again.
+
+
+<h3 id="wakelocks">Using wake locks</h3>
+
+<p>When designing applications that play media
+in the background, the device may go to sleep
+while your service is running. Because the Android system tries to conserve
+battery while the device is sleeping, the system tries to shut off any
+of the phone's features that are
+not necessary, including the CPU and the WiFi hardware.
+However, if your service is playing or streaming music, you want to prevent
+the system from interfering with your playback.</p>
+
+<p>In order to ensure that your service continues to run under
+those conditions, you have to use "wake locks." A wake lock is a way to signal to
+the system that your application is using some feature that should
+stay available even if the phone is idle.</p>
+
+<p class="caution"><strong>Notice:</strong> You should always use wake locks sparingly and hold them
+only for as long as truly necessary, because they significantly reduce the battery life of the
+device.</p>
+
+<p>To ensure that the CPU continues running while your {@link android.media.MediaPlayer} is
+playing, call the {@link android.media.MediaPlayer#setWakeMode
+setWakeMode()} method when initializing your {@link android.media.MediaPlayer}. Once you do,
+the {@link android.media.MediaPlayer} holds the specified lock while playing and releases the lock
+when paused or stopped:</p>
+
+<pre>
+mMediaPlayer = new MediaPlayer();
+// ... other initialization here ...
+mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
+</pre>
+
+<p>However, the wake lock acquired in this example guarantees only that the CPU remains awake. If
+you are streaming media over the
+network and you are using Wi-Fi, you probably want to hold a
+{@link android.net.wifi.WifiManager.WifiLock WifiLock} as
+well, which you must acquire and release manually. So, when you start preparing the
+{@link android.media.MediaPlayer} with the remote URL, you should create and acquire the Wi-Fi lock.
+For example:</p>
+
+<pre>
+WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
+ .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
+
+wifiLock.acquire();
+</pre>
+
+<p>When you pause or stop your media, or when you no longer need the
+network, you should release the lock:</p>
+
+<pre>
+wifiLock.release();
+</pre>
+
+
+<h3 id="foregroundserv">Running as a foreground service</h3>
+
+<p>Services are often used for performing background tasks, such as fetching emails,
+synchronizing data, downloading content, amongst other possibilities. In these
+cases, the user is not actively aware of the service's execution, and probably
+wouldn't even notice if some of these services were interrupted and later restarted.</p>
+
+<p>But consider the case of a service that is playing music. Clearly this is a service that the user
+is actively aware of and the experience would be severely affected by any interruptions.
+Additionally, it's a service that the user will likely wish to interact with during its execution.
+In this case, the service should run as a "foreground service." A
+foreground service holds a higher level of importance within the system—the system will
+almost never kill the service, because it is of immediate importance to the user. When running
+in the foreground, the service also must provide a status bar notification to ensure that users are
+aware of the running service and allow them to open an activity that can interact with the
+service.</p>
+
+<p>In order to turn your service into a foreground service, you must create a
+{@link android.app.Notification Notification} for the status bar and call
+{@link android.app.Service#startForeground startForeground()} from the {@link
+android.app.Service}. For example:</p>
+
+<pre>String songName;
+// assign the song name to songName
+PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
+ new Intent(getApplicationContext(), MainActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+Notification notification = new Notification();
+notification.tickerText = text;
+notification.icon = R.drawable.play0;
+notification.flags |= Notification.FLAG_ONGOING_EVENT;
+notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
+ "Playing: " + songName, pi);
+startForeground(NOTIFICATION_ID, notification);
+</pre>
+
+<p>While your service is running in the foreground, the notification you
+configured is visible in the notification area of the device. If the user
+selects the notification, the system invokes the {@link android.app.PendingIntent} you supplied. In
+the example above, it opens an activity ({@code MainActivity}).</p>
+
+<p>Figure 1 shows how your notification appears to the user:</p>
+
+<img src='images/notification1.png' />
+
+<img src='images/notification2.png' />
+<p class="img-caption"><strong>Figure 1.</strong> Screenshots of a foreground service's notification, showing the notification icon in the status bar (left) and the expanded view (right).</p>
+
+<p>You should only hold on to the "foreground service" status while your
+service is actually performing something the user is actively aware of. Once
+that is no longer true, you should release it by calling
+{@link android.app.Service#stopForeground stopForeground()}:</p>
+
+<pre>
+stopForeground(true);
+</pre>
+
+<p>For more information, see the documentation about <a
+href="{@docRoot}guide/topics/fundamentals/services.html#Foreground">Services</a> and
+<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a>.</p>
+
+
+<h3 id="audiofocus">Handling audio focus</h3>
+
+<p>Even though only one activity can run at any given time, Android is a
+multi-tasking environment. This poses a particular challenge to applications
+that use audio, because there is only one audio output and there may be several
+media services competing for its use. Before Android 2.2, there was no built-in
+mechanism to address this issue, which could in some cases lead to a bad user
+experience. For example, when a user is listening to
+music and another application needs to notify the user of something very important,
+the user might not hear the notification tone due to the loud music. Starting with
+Android 2.2, the platform offers a way for applications to negotiate their
+use of the device's audio output. This mechanism is called Audio Focus.</p>
+
+<p>When your application needs to output audio such as music or a notification,
+you should always request audio focus. Once it has focus, it can use the sound output freely, but it should
+always listen for focus changes. If it is notified that it has lost the audio
+focus, it should immediately either kill the audio or lower it to a quiet level
+(known as "ducking"—there is a flag that indicates which one is appropriate) and only resume
+loud playback after it receives focus again.</p>
+
+<p>Audio Focus is cooperative in nature. That is, applications are expected
+(and highly encouraged) to comply with the audio focus guidelines, but the
+rules are not enforced by the system. If an application wants to play loud
+music even after losing audio focus, nothing in the system will prevent that.
+However, the user is more likely to have a bad experience and will be more
+likely to uninstall the misbehaving application.</p>
+
+<p>To request audio focus, you must call
+{@link android.media.AudioManager#requestAudioFocus requestAudioFocus()} from the {@link
+android.media.AudioManager}, as the example below demonstrates:</p>
+
+<pre>
+AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+
+if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ // could not get audio focus.
+}
+</pre>
+
+<p>The first parameter to {@link android.media.AudioManager#requestAudioFocus requestAudioFocus()}
+is an {@link android.media.AudioManager.OnAudioFocusChangeListener
+AudioManager.OnAudioFocusChangeListener},
+whose {@link android.media.AudioManager.OnAudioFocusChangeListener#onAudioFocusChange
+onAudioFocusChange()} method is called whenever there is a change in audio focus. Therefore, you
+should also implement this interface on your service and activities. For example:</p>
+
+<pre>
+class MyService extends Service
+ implements AudioManager.OnAudioFocusChangeListener {
+ // ....
+ public void onAudioFocusChange(int focusChange) {
+ // Do something based on focus change...
+ }
+}
+</pre>
+
+<p>The <code>focusChange</code> parameter tells you how the audio focus has changed, and
+can be one of the following values (they are all constants defined in
+{@link android.media.AudioManager AudioManager}):</p>
+
+<ul>
+<li>{@link android.media.AudioManager#AUDIOFOCUS_GAIN}: You have gained the audio focus.</li>
+
+<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS}: You have lost the audio focus for a
+presumably long time.
+You must stop all audio playback. Because you should expect not to have focus back
+for a long time, this would be a good place to clean up your resources as much
+as possible. For example, you should release the {@link android.media.MediaPlayer}.</li>
+
+<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT}: You have
+temporarily lost audio focus, but should receive it back shortly. You must stop
+all audio playback, but you can keep your resources because you will probably get
+focus back shortly.</li>
+
+<li>{@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}: You have temporarily
+lost audio focus,
+but you are allowed to continue to play audio quietly (at a low volume) instead
+of killing audio completely.</li>
+</ul>
+
+<p>Here is an example implementation:</p>
+
+<pre>
+public void onAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_GAIN:
+ // resume playback
+ if (mMediaPlayer == null) initMediaPlayer();
+ else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
+ mMediaPlayer.setVolume(1.0f, 1.0f);
+ break;
+
+ case AudioManager.AUDIOFOCUS_LOSS:
+ // Lost focus for an unbounded amount of time: stop playback and release media player
+ if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ break;
+
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ // Lost focus for a short time, but we have to stop
+ // playback. We don't release the media player because playback
+ // is likely to resume
+ if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
+ break;
+
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ // Lost focus for a short time, but it's ok to keep playing
+ // at an attenuated level
+ if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
+ break;
+ }
+}
+</pre>
+
+<p>Keep in mind that the audio focus APIs are available only with API level 8 (Android 2.2)
+and above, so if you want to support previous
+versions of Android, you should adopt a backward compatibility strategy that
+allows you to use this feature if available, and fall back seamlessly if not.</p>
+
+<p>You can achieve backward compatibility either by calling the audio focus methods by reflection
+or by implementing all the audio focus features in a separate class (say,
+<code>AudioFocusHelper</code>). Here is an example of such a class:</p>
+
+<pre>
+public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
+ AudioManager mAudioManager;
+
+ // other fields here, you'll probably hold a reference to an interface
+ // that you can use to communicate the focus changes to your Service
+
+ public AudioFocusHelper(Context ctx, /* other arguments here */) {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ // ...
+ }
+
+ public boolean requestFocus() {
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
+ mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
+ AudioManager.AUDIOFOCUS_GAIN);
+ }
+
+ public boolean abandonFocus() {
+ return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
+ mAudioManager.abandonAudioFocus(this);
+ }
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ // let your service know about the focus change
+ }
+}
+</pre>
+
+
+<p>You can create an instance of <code>AudioFocusHelper</code> class only if you detect that
+the system is running API level 8 or above. For example:</p>
+
+<pre>
+if (android.os.Build.VERSION.SDK_INT >= 8) {
+ mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
+} else {
+ mAudioFocusHelper = null;
+}
+</pre>
+
+
+<h3 id="cleanup">Performing cleanup</h3>
+
+<p>As mentioned earlier, a {@link android.media.MediaPlayer} object can consume a significant
+amount of system resources, so you should keep it only for as long as you need and call
+{@link android.media.MediaPlayer#release release()} when you are done with it. It's important
+to call this cleanup method explicitly rather than rely on system garbage collection because
+it might take some time before the garbage collector reclaims the {@link android.media.MediaPlayer},
+as it's only sensitive to memory needs and not to shortage of other media-related resources.
+So, in the case when you're using a service, you should always override the
+{@link android.app.Service#onDestroy onDestroy()} method to make sure you are releasing
+the {@link android.media.MediaPlayer}:</p>
+
+<pre>
+public class MyService extends Service {
+ MediaPlayer mMediaPlayer;
+ // ...
+
+ @Override
+ public void onDestroy() {
+ if (mMediaPlayer != null) mMediaPlayer.release();
+ }
+}
+</pre>
+
+<p>You should always look for other opportunities to release your {@link android.media.MediaPlayer}
+as well, apart from releasing it when being shut down. For example, if you expect not
+to be able to play media for an extended period of time (after losing audio focus, for example),
+you should definitely release your existing {@link android.media.MediaPlayer} and create it again
+later. On the
+other hand, if you only expect to stop playback for a very short time, you should probably
+hold on to your {@link android.media.MediaPlayer} to avoid the overhead of creating and preparing it
+again.</p>
+
+
+
+<h2 id="noisyintent">Handling the AUDIO_BECOMING_NOISY Intent</h2>
+
+<p>Many well-written applications that play audio automatically stop playback when an event
+occurs that causes the audio to become noisy (ouput through external speakers). For instance,
+this might happen when a user is listening to music through headphones and accidentally
+disconnects the headphones from the device. However, this behavior does not happen automatically.
+If you don't implement this feature, audio plays out of the device's external speakers, which
+might not be what the user wants.</p>
+
+<p>You can ensure your app stops playing music in these situations by handling
+the {@link android.media.AudioManager#ACTION_AUDIO_BECOMING_NOISY} intent, for which you can register a receiver by
+adding the following to your manifest:</p>
+
+<pre>
+<receiver android:name=".MusicIntentReceiver">
+ <intent-filter>
+ <action android:name="android.media.AUDIO_BECOMING_NOISY" />
+ </intent-filter>
+</receiver>
+</pre>
+
+<p>This registers the <code>MusicIntentReceiver</code> class as a broadcast receiver for that
+intent. You should then implement this class:</p>
+
+<pre>
+public class MusicIntentReceiver implements android.content.BroadcastReceiver {
+ @Override
+ public void onReceive(Context ctx, Intent intent) {
+ if (intent.getAction().equals(
+ android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
+ // signal your service to stop playback
+ // (via an Intent, for instance)
+ }
+ }
+}
+</pre>
+
+
+
+
+<h2 id="viacontentresolver">Retrieving Media from a Content Resolver</h2>
+
+<p>Another feature that may be useful in a media player application is the ability to
+retrieve music that the user has on the device. You can do that by querying the {@link
+android.content.ContentResolver} for external media:</p>
+
+<pre>
+ContentResolver contentResolver = getContentResolver();
+Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+Cursor cursor = contentResolver.query(uri, null, null, null, null);
+if (cursor == null) {
+ // query failed, handle error.
+} else if (!cursor.moveToFirst()) {
+ // no media on the device
+} else {
+ int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
+ int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
+ do {
+ long thisId = cursor.getLong(idColumn);
+ String thisTitle = cursor.getString(titleColumn);
+ // ...process entry...
+ } while (cursor.moveToNext());
+}
+</pre>
+
+<p>To use this with the {@link android.media.MediaPlayer}, you can do this:</p>
+
+<pre>
+long id = /* retrieve it from somewhere */;
+Uri contentUri = ContentUris.withAppendedId(
+ android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
+
+mMediaPlayer = new MediaPlayer();
+mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
+
+// ...prepare and start...
+</pre>
+
+
+
+<h2 id="jetcontent">Playing JET content</h2>
+
+<p>The Android platform includes a JET engine that lets you add interactive playback of JET audio
+content in your applications. You can create JET content for interactive playback using the
+JetCreator authoring application that ships with the SDK. To play and manage JET content from your
+application, use the {@link android.media.JetPlayer JetPlayer} class.</p>
+
+<p>For a description of JET concepts and instructions on how to use the JetCreator authoring tool,
+see the <a href="{@docRoot}guide/topics/media/jet/jetcreator_manual.html">JetCreator User
+Manual</a>. The tool is available on Windows, OS X, and Linux platforms (Linux does not
+support auditioning of imported assets like with the Windows and OS X versions).
+</p>
+
+<p>Here's an example of how to set up JET playback from a <code>.jet</code> file stored on the SD card:</p>
+
+<pre>
+JetPlayer jetPlayer = JetPlayer.getJetPlayer();
+jetPlayer.loadJetFile("/sdcard/level1.jet");
byte segmentId = 0;
// queue segment 5, repeat once, use General MIDI, transpose by -1 octave
-myJet.queueJetSegment(5, -1, 1, -1, 0, segmentId++);
+jetPlayer.queueJetSegment(5, -1, 1, -1, 0, segmentId++);
// queue segment 2
-myJet.queueJetSegment(2, -1, 0, 0, 0, segmentId++);
+jetPlayer.queueJetSegment(2, -1, 0, 0, 0, segmentId++);
-myJet.play();
+jetPlayer.play();
</pre>
-<p>The SDK includes an example application — JetBoy — that shows how to use {@link android.media.JetPlayer JetPlayer} to create an interactive music soundtrack in your game. It also illustrates how to use JET events to synchronize music and game logic. The application is located at <code><sdk>/platforms/android-1.5/samples/JetBoy</code>.
+<p>The SDK includes an example application — JetBoy — that shows how to use {@link
+android.media.JetPlayer JetPlayer} to create an interactive music soundtrack in your game. It also
+illustrates how to use JET events to synchronize music and game logic. The application is located at
+<code><sdk>/platforms/android-1.5/samples/JetBoy</code>.</p>
-<h2 id="capture">Audio Capture</h2>
-<p>Audio capture from the device is a bit more complicated than audio/video playback, but still fairly simple:</p>
+
+<h2 id="audiocapture">Performing Audio Capture</h2>
+
+<p>Audio capture from the device is a bit more complicated than audio and video playback, but still fairly simple:</p>
<ol>
- <li>Create a new instance of {@link android.media.MediaRecorder android.media.MediaRecorder} using <code>new</code></li>
+ <li>Create a new instance of {@link android.media.MediaRecorder android.media.MediaRecorder}.</li>
<li>Set the audio source using
{@link android.media.MediaRecorder#setAudioSource MediaRecorder.setAudioSource()}. You will probably want to use
- <code>MediaRecorder.AudioSource.MIC</code></li>
+ <code>MediaRecorder.AudioSource.MIC</code>.</li>
<li>Set output file format using
- {@link android.media.MediaRecorder#setOutputFormat MediaRecorder.setOutputFormat()}
+ {@link android.media.MediaRecorder#setOutputFormat MediaRecorder.setOutputFormat()}.
</li>
<li>Set output file name using
- {@link android.media.MediaRecorder#setOutputFile MediaRecorder.setOutputFile()}
+ {@link android.media.MediaRecorder#setOutputFile MediaRecorder.setOutputFile()}.
</li>
- <li>Set the audio encoder using
- {@link android.media.MediaRecorder#setAudioEncoder MediaRecorder.setAudioEncoder()}
+ <li>Set the audio encoder using
+ {@link android.media.MediaRecorder#setAudioEncoder MediaRecorder.setAudioEncoder()}.
</li>
<li>Call {@link android.media.MediaRecorder#prepare MediaRecorder.prepare()}
on the MediaRecorder instance.</li>
- <li>To start audio capture, call
+ <li>To start audio capture, call
{@link android.media.MediaRecorder#start MediaRecorder.start()}. </li>
<li>To stop audio capture, call {@link android.media.MediaRecorder#stop MediaRecorder.stop()}.
<li>When you are done with the MediaRecorder instance, call
@@ -354,3 +968,4 @@
</pre>
+
diff --git a/docs/html/guide/topics/usb/adk.jd b/docs/html/guide/topics/usb/adk.jd
index e4e1215..2e172f5 100644
--- a/docs/html/guide/topics/usb/adk.jd
+++ b/docs/html/guide/topics/usb/adk.jd
@@ -54,6 +54,10 @@
</li>
</ol>
+ <h2>Download</h2>
+ <ol>
+ <li><a href="https://dl-ssl.google.com/android/adk/adk_release_0512.zip">ADK package</a></li>
+ </ol>
<h2>See also</h2>
@@ -66,6 +70,12 @@
<h2>Where to buy</h2>
<ol>
+ <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">
+ Modern Device</a></li>
+
+ <li><a href="http://www.seeedstudio.com/depot/seeeduino-adk-main-board-p-846.html">
+ Seeed Studio</a></li>
+
<li><a href=
"http://www.rt-net.jp/shop/index.php?main_page=product_info&cPath=3_4&products_id=1">
RT Corp</a></li>
@@ -77,8 +87,6 @@
<li><a href="https://store.diydrones.com/ProductDetails.asp?ProductCode=BR-PhoneDrone">
DIY Drones</a></li>
- <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">
- Modern Device</a></li>
</ol>
</div>
</div>
@@ -106,15 +114,22 @@
development boards:</p>
<ul>
+ <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">Modern
+ Device</a> provides an Arduino-compatible board that supports the ADK firmware.</li>
+
+ <li><a href="http://www.seeedstudio.com/depot/seeeduino-adk-main-board-p-846.html">
+ Seeed Studio</a> provides an Arduino-compatible board that supports the ADK firmware.</li>
+
<li><a href="http://www.rt-net.jp/shop/index.php?main_page=product_info&cPath=3_4&products_id=1">
RT Corp</a> provides an Arduino-compatible board based on the Android ADK board design.</li>
+
<li><a href="http://www.microchip.com/android">Microchip</a> provides a A PIC based USB
microcontroller board.</li>
+
<li><a href="https://store.diydrones.com/ProductDetails.asp?ProductCode=BR-PhoneDrone">DIY
Drones</a> provides an Arduino-compatible board geared towards RC (radio controlled) and UAV
(unmanned aerial vehicle) enthusiasts.</li>
- <li><a href="http://shop.moderndevice.com/products/freeduino-usb-host-board">Modern
- Device</a> provides an Arduino-compatible board that supports the ADK firmware.</li>
+
</ul>
<p>We expect more hardware distributers to create a variety of kits, so please stay tuned for
@@ -125,7 +140,7 @@
accessory that is based on the <a href="http://www.arduino.cc/">Arduino open source electronics
prototyping platform</a>, the accessory's hardware design files, code that implements the
accessory's firmware, and the Android application that interacts with the accessory. The hardware
- design files and firmware code are contained in the <a href=
+ design files and firmware code are contained in the <a href=ctive
"https://dl-ssl.google.com/android/adk/adk_release_0512.zip">ADK package download</a>.</p>
<p>The main hardware and software components of the ADK include:</p>
diff --git a/docs/html/guide/topics/wireless/bluetooth.jd b/docs/html/guide/topics/wireless/bluetooth.jd
index 48632ea..0af1d2c 100644
--- a/docs/html/guide/topics/wireless/bluetooth.jd
+++ b/docs/html/guide/topics/wireless/bluetooth.jd
@@ -1,57 +1,61 @@
page.title=Bluetooth
@jd:body
-<div id="qv-wrapper">
-<div id="qv">
-
- <h2>Quickview</h2>
- <ul>
+<div id="qv-wrapper">
+<div id="qv">
+
+ <h2>Quickview</h2>
+ <ul>
<li>Android's bluetooth APIs allow your application to perform wireless data transactions with
-other devices</li>
- </ul>
-
- <h2>In this document</h2>
- <ol>
- <li><a href="#TheBasics">The Basics</a></li>
- <li><a href="#Permissions">Bluetooth Permissions</a></li>
- <li><a href="#SettingUp">Setting Up Bluetooth</a></li>
- <li><a href="#FindingDevices">Finding Devices</a>
- <ol>
- <li><a href="#QueryingPairedDevices">Querying paired devices</a></li>
- <li><a href="#DiscoveringDevices">Discovering devices</a></li>
- </ol></li>
- <li><a href="#ConnectingDevices">Connecting Devices</a>
- <ol>
- <li><a href="#ConnectingAsAServer">Connecting as a server</a></li>
- <li><a href="#ConnectingAsAClient">Connecting as a client</a></li>
- </ol></li>
+other devices</li>
+ </ul>
+
+ <h2>In this document</h2>
+ <ol>
+ <li><a href="#TheBasics">The Basics</a></li>
+ <li><a href="#Permissions">Bluetooth Permissions</a></li>
+ <li><a href="#SettingUp">Setting Up Bluetooth</a></li>
+ <li><a href="#FindingDevices">Finding Devices</a>
+ <ol>
+ <li><a href="#QueryingPairedDevices">Querying paired devices</a></li>
+ <li><a href="#DiscoveringDevices">Discovering devices</a></li>
+ </ol></li>
+ <li><a href="#ConnectingDevices">Connecting Devices</a>
+ <ol>
+ <li><a href="#ConnectingAsAServer">Connecting as a server</a></li>
+ <li><a href="#ConnectingAsAClient">Connecting as a client</a></li>
+ </ol></li>
<li><a href="#ManagingAConnection">Managing a Connection</a></li>
- </ol>
-
- <h2>Key classes</h2>
- <ol>
- <li>{@link android.bluetooth.BluetoothAdapter}</li>
- <li>{@link android.bluetooth.BluetoothDevice}</li>
- <li>{@link android.bluetooth.BluetoothSocket}</li>
- <li>{@link android.bluetooth.BluetoothServerSocket}</li>
- </ol>
-
- <h2>Related samples</h2>
- <ol>
- <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat</a></li>
- </ol>
-
-</div>
-</div>
-
-
+ <li><a href="#Profiles">Working with Profiles</a>
+ <ol>
+ <li><a href="#AT-Commands">Vendor-specific AT commands</a>
+ </ol></li>
+ </ol>
+
+ <h2>Key classes</h2>
+ <ol>
+ <li>{@link android.bluetooth.BluetoothAdapter}</li>
+ <li>{@link android.bluetooth.BluetoothDevice}</li>
+ <li>{@link android.bluetooth.BluetoothSocket}</li>
+ <li>{@link android.bluetooth.BluetoothServerSocket}</li>
+ </ol>
+
+ <h2>Related samples</h2>
+ <ol>
+ <li><a href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat</a></li>
+ </ol>
+
+</div>
+</div>
+
+
<p>The Android platform includes support for the Bluetooth network stack,
which allows a device to wirelessly exchange data with other Bluetooth devices.
The application framework provides access to the Bluetooth functionality through
the Android Bluetooth APIs. These APIs let applications wirelessly
connect to other Bluetooth devices, enabling point-to-point and multipoint
-wireless features.</p>
-
+wireless features.</p>
+
<p>Using the Bluetooth APIs, an Android application can perform the
following:</p>
<ul>
@@ -66,17 +70,17 @@
<h2 id="TheBasics">The Basics</h2>
-<p>This document describes how to us the Android Bluetooth APIs to accomplish
+<p>This document describes how to use the Android Bluetooth APIs to accomplish
the four major tasks necessary to communicate using Bluetooth: setting up
Bluetooth, finding devices that are either paired or available in the local
-area, connecting devices, and transferring data between devices.</p>
-
+area, connecting devices, and transferring data between devices.</p>
+
<p>All of the Bluetooth APIs are available in the {@link android.bluetooth}
-package. Here's a summary of the classes you will need to create Bluetooth
-connections:</p>
-
-<dl>
-<dt>{@link android.bluetooth.BluetoothAdapter}</dt>
+package. Here's a summary of the classes and interfaces you will need to create Bluetooth
+connections:</p>
+
+<dl>
+<dt>{@link android.bluetooth.BluetoothAdapter}</dt>
<dd>Represents the local Bluetooth adapter (Bluetooth radio). The
{@link android.bluetooth.BluetoothAdapter} is the entry-point for all Bluetooth
interaction. Using this,
@@ -84,51 +88,72 @@
devices, instantiate a {@link android.bluetooth.BluetoothDevice} using a known
MAC address, and create a {@link android.bluetooth.BluetoothServerSocket} to
listen for communications
-from other devices.</dd>
-
-<dt>{@link android.bluetooth.BluetoothDevice}</dt>
+from other devices.</dd>
+
+<dt>{@link android.bluetooth.BluetoothDevice}</dt>
<dd>Represents a remote Bluetooth device. Use this to request a connection
with a remote device through a {@link android.bluetooth.BluetoothSocket} or
query information about the
-device such as its name, address, class, and bonding state.</dd>
-
-<dt>{@link android.bluetooth.BluetoothSocket}</dt>
+device such as its name, address, class, and bonding state.</dd>
+
+<dt>{@link android.bluetooth.BluetoothSocket}</dt>
<dd>Represents the interface for a Bluetooth socket (similar to a TCP
{@link java.net.Socket}). This is the connection point that allows
an application to exchange data with another Bluetooth device via InputStream
-and OutputStream.</dd>
-
-<dt>{@link android.bluetooth.BluetoothServerSocket}</dt>
+and OutputStream.</dd>
+
+<dt>{@link android.bluetooth.BluetoothServerSocket}</dt>
<dd>Represents an open server socket that listens for incoming requests
(similar to a TCP {@link java.net.ServerSocket}). In order to connect two
Android devices, one device must open a server socket with this class. When a
remote Bluetooth device makes a connection request to the this device, the
{@link android.bluetooth.BluetoothServerSocket} will return a connected {@link
android.bluetooth.BluetoothSocket} when the
-connection is accepted.</dd>
-
-<dt>{@link android.bluetooth.BluetoothClass}</dt>
+connection is accepted.</dd>
+
+<dt>{@link android.bluetooth.BluetoothClass}</dt>
<dd>Describes the general characteristics and capabilities of a Bluetooth
device. This is a read-only set of properties that define the device's major and
minor device classes and its services. However, this does not reliably describe
all Bluetooth profiles and services supported by the device, but is useful as a
-hint to the device type.</dd>
-</dl>
+hint to the device type.</dd>
+
+<dt>{@link android.bluetooth.BluetoothProfile}</dt> <dd>An interface that
+represents a Bluetooth profile. A <em>Bluetooth profile</em> is a wireless
+interface specification for Bluetooth-based communication between devices. An
+example is the Hands-Free profile. For more discussion of profiles, see <a
+href="#Profiles">Working with Profiles</a></dd>
+<dt>{@link android.bluetooth.BluetoothHeadset}</dt> <dd>Provides support for
+Bluetooth headsets to be used with mobile phones. This includes both Bluetooth
+Headset and Hands-Free (v1.5) profiles.</dd>
+<dt>{@link android.bluetooth.BluetoothA2dp}</dt> <dd> Defines how high quality
+audio can be streamed from one device to another over a Bluetooth connection.
+"A2DP" stands for Advanced Audio Distribution Profile.</dd>
+<dt>{@link android.bluetooth.BluetoothProfile.ServiceListener}</dt>
-<h2 id="Permissions">Bluetooth Permissions</h2>
-
+<dd>An interface that notifies {@link android.bluetooth.BluetoothProfile} IPC
+clients when they have been connected to or disconnected from the service (that
+is, the internal service that runs a particular profile). </dd>
+
+</dl>
+
+
+
+
+<h2 id="Permissions">Bluetooth Permissions</h2>
+
<p>In order to use Bluetooth features in your application, you need to declare
at least one of two Bluetooth permissions: {@link
android.Manifest.permission#BLUETOOTH} and {@link
-android.Manifest.permission#BLUETOOTH_ADMIN}.</p>
-
+android.Manifest.permission#BLUETOOTH_ADMIN}.</p>
+
<p>You must request the {@link android.Manifest.permission#BLUETOOTH} permission
in order to perform any Bluetooth communication, such as requesting a
-connection, accepting a connection, and transferring data.</p>
-
+connection, accepting a connection, and transferring data.</p>
+
<p>You must request the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
permission in order to initiate device discovery or manipulate Bluetooth
settings. Most applications need this permission solely for the
@@ -136,40 +161,40 @@
permission should not be used, unless the application is a "power manager" that
will modify Bluetooth settings upon user request. <strong>Note:</strong> If you
use {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission, then must
-also have the {@link android.Manifest.permission#BLUETOOTH} permission.</p>
-
+also have the {@link android.Manifest.permission#BLUETOOTH} permission.</p>
+
<p>Declare the Bluetooth permission(s) in your application manifest file. For
-example:</p>
-
-<pre>
+example:</p>
+
+<pre>
<manifest ... >
<uses-permission android:name="android.permission.BLUETOOTH" />
...
</manifest>
-</pre>
-
+</pre>
+
<p>See the <a
-href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a>
-reference for more information about declaring application permissions.</p>
-
-
-<h2 id="SettingUp">Setting Up Bluetooth</h2>
-
-<div class="figure" style="width:200px">
-<img src="{@docRoot}images/bt_enable_request.png" />
+href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a>
+reference for more information about declaring application permissions.</p>
+
+
+<h2 id="SettingUp">Setting Up Bluetooth</h2>
+
+<div class="figure" style="width:200px">
+<img src="{@docRoot}images/bt_enable_request.png" />
<strong>Figure 1:</strong> The enabling Bluetooth dialog.
-</div>
-
+</div>
+
<p>Before your application can communicate over Bluetooth, you need to verify
-that Bluetooth is supported on the device, and if so, ensure that it is enabled.</p>
-
+that Bluetooth is supported on the device, and if so, ensure that it is enabled.</p>
+
<p>If Bluetooth is not supported, then you should gracefully disable any
Bluetooth features. If Bluetooth is supported, but disabled, then you can request that the
user enable Bluetooth without leaving your application. This setup is
-accomplished in two steps, using the {@link android.bluetooth.BluetoothAdapter}.</p>
-
-
-<ol>
+accomplished in two steps, using the {@link android.bluetooth.BluetoothAdapter}.</p>
+
+
+<ol>
<li>Get the {@link android.bluetooth.BluetoothAdapter}
<p>The {@link android.bluetooth.BluetoothAdapter} is required for any and all Bluetooth
activity. To get the {@link android.bluetooth.BluetoothAdapter}, call the static {@link
@@ -178,15 +203,15 @@
Bluetooth adapter (the Bluetooth radio). There's one Bluetooth adapter for the
entire system, and your application can interact with it using this object. If
{@link android.bluetooth.BluetoothAdapter#getDefaultAdapter()} returns null,
-then the device does not support Bluetooth and your story ends here. For example:</p>
-<pre>
+then the device does not support Bluetooth and your story ends here. For example:</p>
+<pre>
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// Device does not support Bluetooth
}
-</pre>
-</li>
-
+</pre>
+</li>
+
<li>Enable Bluetooth
<p>Next, you need to ensure that Bluetooth is enabled. Call {@link
android.bluetooth.BluetoothAdapter#isEnabled()} to check whether Bluetooth is
@@ -195,26 +220,26 @@
android.app.Activity#startActivityForResult(Intent,int) startActivityForResult()}
with the {@link android.bluetooth.BluetoothAdapter#ACTION_REQUEST_ENABLE} action Intent.
This will issue a request to enable Bluetooth through the system settings (without
-stopping your application). For example:</p>
-<pre>
+stopping your application). For example:</p>
+<pre>
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
-</pre>
-
+</pre>
+
<p>A dialog will appear requesting user permission to enable Bluetooth, as shown
in Figure 1. If the user responds "Yes," the system will begin to enable Bluetooth
-and focus will return to your application once the process completes (or fails).</p>
+and focus will return to your application once the process completes (or fails).</p>
<p>If enabling Bluetooth succeeds, your Activity will receive the {@link
android.app.Activity#RESULT_OK} result code in the {@link
android.app.Activity#onActivityResult(int,int,Intent) onActivityResult()}
callback. If Bluetooth was not enabled
due to an error (or the user responded "No") then the result code will be {@link
-android.app.Activity#RESULT_CANCELED}.</p>
-</li>
-</ol>
-
+android.app.Activity#RESULT_CANCELED}.</p>
+</li>
+</ol>
+
<p>Optionally, your application can also listen for the
{@link android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED} broadcast Intent, which
the system will broadcast whenever the Bluetooth state has changed. This broadcast contains
@@ -226,21 +251,21 @@
android.bluetooth.BluetoothAdapter#STATE_TURNING_OFF}, and {@link
android.bluetooth.BluetoothAdapter#STATE_OFF}. Listening for this
broadcast can be useful to detect changes made to the Bluetooth state while your
-app is running.</p>
-
+app is running.</p>
+
<p class="note"><strong>Tip:</strong> Enabling discoverability will automatically
enable Bluetooth. If you plan to consistently enable device discoverability before
performing Bluetooth activity, you can skip
step 2 above. Read about <a href="#EnablingDiscoverability">enabling discoverability</a>,
-below.</p>
-
-
-<h2 id="FindingDevices">Finding Devices</h2>
-
+below.</p>
+
+
+<h2 id="FindingDevices">Finding Devices</h2>
+
<p>Using the {@link android.bluetooth.BluetoothAdapter}, you can find remote Bluetooth
devices either through device discovery or by querying the list of paired (bonded)
-devices.</p>
-
+devices.</p>
+
<p>Device discovery is a scanning procedure that searches the local area for
Bluetooth enabled devices and then requesting some information about each one
(this is sometimes referred to as "discovering," "inquiring" or "scanning").
@@ -249,15 +274,15 @@
discoverable, it will respond to the discovery request by sharing some
information, such as the device name, class, and its unique MAC address. Using
this information, the device performing discovery can then choose to initiate a
-connection to the discovered device.</p>
-
+connection to the discovered device.</p>
+
<p>Once a connection is made with a remote device for the first time, a pairing
request is automatically presented to the user. When a device is
paired, the basic information about that device (such as the device name, class,
and MAC address) is saved and can be read using the Bluetooth APIs. Using the
known MAC address for a remote device, a connection can be initiated with it at
-any time without performing discovery (assuming the device is within range).</p>
-
+any time without performing discovery (assuming the device is within range).</p>
+
<p>Remember there is a difference between being paired and being connected. To
be paired means that two devices are aware of each other's existence, have a
shared link-key that can be used for authentication, and are capable of
@@ -265,28 +290,28 @@
the devices currently share an RFCOMM channel and are able to transmit data with
each other. The current Android Bluetooth API's require devices to be paired
before an RFCOMM connection can be established. (Pairing is automatically performed
-when you initiate an encrypted connection with the Bluetooth APIs.)</p>
-
+when you initiate an encrypted connection with the Bluetooth APIs.)</p>
+
<p>The following sections describe how to find devices that have been paired, or
-discover new devices using device discovery.</p>
-
+discover new devices using device discovery.</p>
+
<p class="note"><strong>Note:</strong> Android-powered devices are not
discoverable by default. A user can make
the device discoverable for a limited time through the system settings, or an
application can request that the user enable discoverability without leaving the
-application. How to <a href="#EnablingDiscoverability">enable discoverability</a>
-is discussed below.</p>
-
-
-<h3 id="QueryingPairedDevices">Querying paired devices</h3>
-
+application. How to <a href="#EnablingDiscoverability">enable discoverability</a>
+is discussed below.</p>
+
+
+<h3 id="QueryingPairedDevices">Querying paired devices</h3>
+
<p>Before performing device discovery, its worth querying the set
of paired devices to see if the desired device is already known. To do so,
call {@link android.bluetooth.BluetoothAdapter#getBondedDevices()}. This
will return a Set of {@link android.bluetooth.BluetoothDevice}s representing
paired devices. For example, you can query all paired devices and then
-show the name of each device to the user, using an ArrayAdapter:</p>
-<pre>
+show the name of each device to the user, using an ArrayAdapter:</p>
+<pre>
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
@@ -296,24 +321,24 @@
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
-</pre>
-
+</pre>
+
<p>All that's needed from the {@link android.bluetooth.BluetoothDevice} object
in order to initiate a connection is the MAC address. In this example, it's saved
as a part of an ArrayAdapter that's shown to the user. The MAC address can later
be extracted in order to initiate the connection. You can learn more about creating
-a connection in the section about <a href="#ConnectingDevices">Connecting Devices</a>.</p>
-
-
-<h3 id="DiscoveringDevices">Discovering devices</h3>
-
+a connection in the section about <a href="#ConnectingDevices">Connecting Devices</a>.</p>
+
+
+<h3 id="DiscoveringDevices">Discovering devices</h3>
+
<p>To start discovering devices, simply call {@link
android.bluetooth.BluetoothAdapter#startDiscovery()}. The
process is asynchronous and the method will immediately return with a boolean
indicating whether discovery has successfully started. The discovery process
usually involves an inquiry scan of about 12 seconds, followed by a page scan of
-each found device to retrieve its Bluetooth name.</p>
-
+each found device to retrieve its Bluetooth name.</p>
+
<p>Your application must register a BroadcastReceiver for the
{@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent in
order to receive information about each
@@ -324,8 +349,8 @@
{@link android.bluetooth.BluetoothDevice#EXTRA_CLASS}, containing a
{@link android.bluetooth.BluetoothDevice} and a {@link
android.bluetooth.BluetoothClass}, respectively. For example, here's how you can
-register to handle the broadcast when devices are discovered:</p>
-<pre>
+register to handle the broadcast when devices are discovered:</p>
+<pre>
// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
@@ -342,15 +367,15 @@
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter); // Don't forget to unregister during onDestroy
-</pre>
-
+</pre>
+
<p>All that's needed from the {@link android.bluetooth.BluetoothDevice} object
in order to initiate a
connection is the MAC address. In this example, it's saved as a part of an
ArrayAdapter that's shown to the user. The MAC address can later be extracted in
order to initiate the connection. You can learn more about creating a connection
-in the section about <a href="#ConnectingDevices">Connecting Devices</a>.</p>
-
+in the section about <a href="#ConnectingDevices">Connecting Devices</a>.</p>
+
<p class="caution"><strong>Caution:</strong> Performing device discovery is
a heavy procedure for the Bluetooth
adapter and will consume a lot of its resources. Once you have found a device to
@@ -359,41 +384,44 @@
attempting a connection. Also, if you
already hold a connection with a device, then performing discovery can
significantly reduce the bandwidth available for the connection, so you should
-not perform discovery while connected.</p>
-
-<h4 id="EnablingDiscoverability">Enabling discoverability</h4>
-
+not perform discovery while connected.</p>
+
+<h4 id="EnablingDiscoverability">Enabling discoverability</h4>
+
<p>If you would like to make the local device discoverable to other devices,
call {@link android.app.Activity#startActivityForResult(Intent,int)} with the
-{@link android.bluetooth.BluetoothAdapter#ACTION_REQUEST_DISCOVERABLE} action Intent.
-This will issue a request to enable discoverable mode through the system settings (without
-stopping your application). By default, the device will become discoverable for
-120 seconds. You can define a different duration by adding the
-{@link android.bluetooth.BluetoothAdapter#EXTRA_DISCOVERABLE_DURATION} Intent extra
-(maximum duration is 300 seconds). For example:</p>
-<pre>
-Intent discoverableIntent = new
+{@link android.bluetooth.BluetoothAdapter#ACTION_REQUEST_DISCOVERABLE} action
+Intent. This will issue a request to enable discoverable mode through the system
+settings (without stopping your application). By default, the device will become
+discoverable for 120 seconds. You can define a different duration by adding the
+{@link android.bluetooth.BluetoothAdapter#EXTRA_DISCOVERABLE_DURATION} Intent
+extra. The maximum duration an app can set is 3600 seconds, and a value of 0
+means the device is always discoverable. Any value below 0 or above 3600 is
+automatically set to 120 secs). For example, this snippet sets the duration to
+300:</p>
+
+<pre>Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
-</pre>
-
-<div class="figure" style="width:200px">
-<img src="{@docRoot}images/bt_enable_discoverable.png" />
+</pre>
+
+<div class="figure" style="width:200px">
+<img src="{@docRoot}images/bt_enable_discoverable.png" />
<strong>Figure 2:</strong> The enabling discoverability dialog.
-</div>
-
+</div>
+
<p>A dialog will be displayed, requesting user permission to make the device
discoverable, as shown in Figure 2. If the user responds "Yes," then the device
will become discoverable for the specified amount of time. Your Activity will
then receive a call to the {@link android.app.Activity#onActivityResult(int,int,Intent)
onActivityResult())} callback, with the result code equal to the duration that the device
is discoverable. If the user responded "No" or if an error occurred, the result code will
-be Activity.RESULT_CANCELLED.</p>
-
+be Activity.RESULT_CANCELLED.</p>
+
<p class="note"><strong>Note:</strong> If Bluetooth has not been enabled on the device,
-then enabling device discoverability will automatically enable Bluetooth.</p>
-
+then enabling device discoverability will automatically enable Bluetooth.</p>
+
<p>The device will silently remain in discoverable mode for the allotted time.
If you would like to be notified when the discoverable mode has changed, you can
register a BroadcastReceiver for the {@link
@@ -407,18 +435,18 @@
android.bluetooth.BluetoothAdapter#SCAN_MODE_NONE},
which indicate that the device is either in discoverable mode, not in
discoverable mode but still able to receive connections, or not in discoverable
-mode and unable to receive connections, respectively.</p>
-
+mode and unable to receive connections, respectively.</p>
+
<p>You do not need to enable device discoverability if you will be initiating
the connection to a remote device. Enabling discoverability is only necessary when
you want your application to host a server socket that will accept incoming
connections, because the remote devices must be able to discover the device
-before it can initiate the connection.</p>
-
-
-
-<h2 id="ConnectingDevices">Connecting Devices</h2>
-
+before it can initiate the connection.</p>
+
+
+
+<h2 id="ConnectingDevices">Connecting Devices</h2>
+
<p>In order to create a connection between your application on two devices, you
must implement both the server-side and client-side mechanisms, because one
device must open a server socket and the other one must initiate the connection
@@ -428,36 +456,36 @@
point, each device can obtain input and output streams and data transfer can
begin, which is discussed in the section about <a
href="#ManagingAConnection">Managing a Connection</a>. This section describes how
-to initiate the connection between two devices.</p>
-
+to initiate the connection between two devices.</p>
+
<p>The server device and the client device each obtain the required {@link
android.bluetooth.BluetoothSocket} in different ways. The server will receive it
when an incoming connection is accepted. The client will receive it when it
-opens an RFCOMM channel to the server.</p>
-
-<div class="figure" style="width:200px">
-<img src="{@docRoot}images/bt_pairing_request.png" />
+opens an RFCOMM channel to the server.</p>
+
+<div class="figure" style="width:200px">
+<img src="{@docRoot}images/bt_pairing_request.png" />
<strong>Figure 3:</strong> The Bluetooth pairing dialog.
-</div>
-
+</div>
+
<p>One implementation technique is to automatically prepare each device as a
server, so that each one has a server socket open and listening for connections.
Then either device can initiate a connection with the other and become the
client. Alternatively, one device can explicitly "host" the connection and open
a server socket on demand and the other device can simply initiate the
-connection.</p>
-
+connection.</p>
+
<p class="note"><strong>Note:</strong> If the two devices have not been previously paired,
then the Android framework will automatically show a pairing request notification or
dialog to the user during the connection procedure, as shown in Figure 3. So
when attempting to connect devices,
your application does not need to be concerned about whether or not the devices are
paired. Your RFCOMM connection attempt will block until the user has successfully paired,
-or will fail if the user rejects pairing, or if pairing fails or times out. </p>
-
-
-<h3 id="ConnectingAsAServer">Connecting as a server</h3>
-
+or will fail if the user rejects pairing, or if pairing fails or times out. </p>
+
+
+<h3 id="ConnectingAsAServer">Connecting as a server</h3>
+
<p>When you want to connect two devices, one must act as a server by holding an
open {@link android.bluetooth.BluetoothServerSocket}. The purpose of the server
socket is to listen for incoming connection requests and when one is accepted,
@@ -465,26 +493,26 @@
android.bluetooth.BluetoothSocket} is acquired from the {@link
android.bluetooth.BluetoothServerSocket},
the {@link android.bluetooth.BluetoothServerSocket} can (and should) be
-discarded, unless you want to accept more connections.</p>
-
-<div class="sidebox-wrapper">
-<div class="sidebox">
-<h2>About UUID</h2>
-
+discarded, unless you want to accept more connections.</p>
+
+<div class="sidebox-wrapper">
+<div class="sidebox">
+<h2>About UUID</h2>
+
<p>A Universally Unique Identifier (UUID) is a standardized 128-bit format for a string
ID used to uniquely identify information. The point of a UUID is that it's big
enough that you can select any random and it won't clash. In this case, it's
used to uniquely identify your application's Bluetooth service. To get a UUID to
use with your application, you can use one of the many random UUID generators on
the web, then initialize a {@link java.util.UUID} with {@link
-java.util.UUID#fromString(String)}.</p>
-</div>
-</div>
-
+java.util.UUID#fromString(String)}.</p>
+</div>
+</div>
+
<p>Here's the basic procedure to set up a server socket and accept a
-connection:</p>
-
-<ol>
+connection:</p>
+
+<ol>
<li>Get a {@link android.bluetooth.BluetoothServerSocket} by calling the
{@link
android.bluetooth.BluetoothAdapter#listenUsingRfcommWithServiceRecord(String,
@@ -496,9 +524,9 @@
agreement with the client device. That is, when the client attempts to connect
with this device, it will carry a UUID that uniquely identifies the service with
which it wants to connect. These UUIDs must match in order for the connection to
-be accepted (in the next step).</p>
-</li>
-
+be accepted (in the next step).</p>
+</li>
+
<li>Start listening for connection requests by calling
{@link android.bluetooth.BluetoothServerSocket#accept()}.
<p>This is a blocking call. It will return when either a connection has been
@@ -506,9 +534,9 @@
remote device has sent a connection request with a UUID matching the one
registered with this listening server socket. When successful, {@link
android.bluetooth.BluetoothServerSocket#accept()} will
-return a connected {@link android.bluetooth.BluetoothSocket}.</p>
-</li>
-
+return a connected {@link android.bluetooth.BluetoothSocket}.</p>
+</li>
+
<li>Unless you want to accept additional connections, call
{@link android.bluetooth.BluetoothServerSocket#close()}.
<p>This releases the server socket and all its resources, but does <em>not</em> close the
@@ -517,10 +545,10 @@
connected client per channel at a time, so in most cases it makes sense to call {@link
android.bluetooth.BluetoothServerSocket#close()} on the {@link
android.bluetooth.BluetoothServerSocket} immediately after accepting a connected
-socket.</p>
-</li>
-</ol>
-
+socket.</p>
+</li>
+</ol>
+
<p>The {@link android.bluetooth.BluetoothServerSocket#accept()} call should not
be executed in the main Activity UI thread because it is a blocking call and
will prevent any other interaction with the application. It usually makes
@@ -533,16 +561,16 @@
android.bluetooth.BluetoothSocket}) from another thread and the blocked call will
immediately return. Note that all methods on a {@link
android.bluetooth.BluetoothServerSocket} or {@link android.bluetooth.BluetoothSocket}
-are thread-safe.</p>
-
-<h4>Example</h4>
-
+are thread-safe.</p>
+
+<h4>Example</h4>
+
<p>Here's a simplified thread for the server component that accepts incoming
-connections:</p>
-<pre>
+connections:</p>
+<pre>
private class AcceptThread extends Thread {
private final BluetoothServerSocket mmServerSocket;
-
+
public AcceptThread() {
// Use a temporary object that is later assigned to mmServerSocket,
// because mmServerSocket is final
@@ -553,7 +581,7 @@
} catch (IOException e) { }
mmServerSocket = tmp;
}
-
+
public void run() {
BluetoothSocket socket = null;
// Keep listening until exception occurs or a socket is returned
@@ -572,7 +600,7 @@
}
}
}
-
+
/** Will cancel the listening socket, and cause the thread to finish */
public void cancel() {
try {
@@ -580,37 +608,37 @@
} catch (IOException e) { }
}
}
-</pre>
-
+</pre>
+
<p>In this example, only one incoming connection is desired, so as soon as a
connection is accepted and the {@link android.bluetooth.BluetoothSocket} is
acquired, the application
sends the acquired {@link android.bluetooth.BluetoothSocket} to a separate
thread, closes the
-{@link android.bluetooth.BluetoothServerSocket} and breaks the loop.</p>
-
+{@link android.bluetooth.BluetoothServerSocket} and breaks the loop.</p>
+
<p>Note that when {@link android.bluetooth.BluetoothServerSocket#accept()}
returns the {@link android.bluetooth.BluetoothSocket}, the socket is already
connected, so you should <em>not</em> call {@link
android.bluetooth.BluetoothSocket#connect()} (as you do from the
-client-side).</p>
-
+client-side).</p>
+
<p><code>manageConnectedSocket()</code> is a fictional method in the application
that will
initiate the thread for transferring data, which is discussed in the section
-about <a href="#ManagingAConnection">Managing a Connection</a>.</p>
-
+about <a href="#ManagingAConnection">Managing a Connection</a>.</p>
+
<p>You should usually close your {@link android.bluetooth.BluetoothServerSocket}
as soon as you are done listening for incoming connections. In this example, {@link
android.bluetooth.BluetoothServerSocket#close()} is called as soon
as the {@link android.bluetooth.BluetoothSocket} is acquired. You may also want
to provide a public method in your thread that can close the private {@link
android.bluetooth.BluetoothSocket} in the event that you need to stop listening on the
-server socket.</p>
-
-
-<h3 id="ConnectingAsAClient">Connecting as a client</h3>
-
+server socket.</p>
+
+
+<h3 id="ConnectingAsAClient">Connecting as a client</h3>
+
<p>In order to initiate a connection with a remote device (a device holding an
open
server socket), you must first obtain a {@link
@@ -619,11 +647,11 @@
section about <a
href="#FindingDevices">Finding Devices</a>.) You must then use the
{@link android.bluetooth.BluetoothDevice} to acquire a {@link
-android.bluetooth.BluetoothSocket} and initiate the connection.</p>
-
-<p>Here's the basic procedure:</p>
-
-<ol>
+android.bluetooth.BluetoothSocket} and initiate the connection.</p>
+
+<p>Here's the basic procedure:</p>
+
+<ol>
<li>Using the {@link android.bluetooth.BluetoothDevice}, get a {@link
android.bluetooth.BluetoothSocket} by calling {@link
android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(UUID)}.
@@ -634,9 +662,9 @@
android.bluetooth.BluetoothAdapter#listenUsingRfcommWithServiceRecord(String,
UUID)}). Using the same UUID is simply a matter of hard-coding the UUID string
into your application and then referencing it from both the server and client
-code.</p>
-</li>
-
+code.</p>
+</li>
+
<li>Initiate the connection by calling {@link
android.bluetooth.BluetoothSocket#connect()}.
<p>Upon this call, the system will perform an SDP lookup on the remote device in
@@ -647,34 +675,34 @@
blocking call. If, for
any reason, the connection fails or the {@link
android.bluetooth.BluetoothSocket#connect()} method times out (after about
-12 seconds), then it will throw an exception.</p>
+12 seconds), then it will throw an exception.</p>
<p>Because {@link
android.bluetooth.BluetoothSocket#connect()} is a blocking call, this connection
procedure should always be performed in a thread separate from the main Activity
-thread.</p>
+thread.</p>
<p class="note">Note: You should always ensure that the device is not performing
device discovery when you call {@link
android.bluetooth.BluetoothSocket#connect()}. If discovery is in progress, then
the
-connection attempt will be significantly slowed and is more likely to fail.</p>
-</li>
-</ol>
-
-<h4>Example</h4>
-
+connection attempt will be significantly slowed and is more likely to fail.</p>
+</li>
+</ol>
+
+<h4>Example</h4>
+
<p>Here is a basic example of a thread that initiates a Bluetooth
-connection:</p>
-<pre>
+connection:</p>
+<pre>
private class ConnectThread extends Thread {
private final BluetoothSocket mmSocket;
private final BluetoothDevice mmDevice;
-
+
public ConnectThread(BluetoothDevice device) {
// Use a temporary object that is later assigned to mmSocket,
// because mmSocket is final
BluetoothSocket tmp = null;
mmDevice = device;
-
+
// Get a BluetoothSocket to connect with the given BluetoothDevice
try {
// MY_UUID is the app's UUID string, also used by the server code
@@ -682,11 +710,11 @@
} catch (IOException e) { }
mmSocket = tmp;
}
-
+
public void run() {
// Cancel discovery because it will slow down the connection
mBluetoothAdapter.cancelDiscovery();
-
+
try {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
@@ -698,11 +726,11 @@
} catch (IOException closeException) { }
return;
}
-
+
// Do work to manage the connection (in a separate thread)
manageConnectedSocket(mmSocket);
}
-
+
/** Will cancel an in-progress connection, and close the socket */
public void cancel() {
try {
@@ -710,42 +738,42 @@
} catch (IOException e) { }
}
}
-</pre>
-
+</pre>
+
<p>Notice that {@link android.bluetooth.BluetoothAdapter#cancelDiscovery()} is called
before the connection is made. You should always do this before connecting and it is safe
to call without actually checking whether it is running or not (but if you do want to
-check, call {@link android.bluetooth.BluetoothAdapter#isDiscovering()}).</p>
-
+check, call {@link android.bluetooth.BluetoothAdapter#isDiscovering()}).</p>
+
<p><code>manageConnectedSocket()</code> is a fictional method in the application
that will initiate the thread for transferring data, which is discussed in the section
-about <a href="#ManagingAConnection">Managing a Connection</a>.</p>
-
+about <a href="#ManagingAConnection">Managing a Connection</a>.</p>
+
<p>When you're done with your {@link android.bluetooth.BluetoothSocket}, always
call {@link android.bluetooth.BluetoothSocket#close()} to clean up.
Doing so will immediately close the connected socket and clean up all internal
-resources.</p>
-
-
-<h2 id="ManagingAConnection">Managing a Connection</h2>
-
+resources.</p>
+
+
+<h2 id="ManagingAConnection">Managing a Connection</h2>
+
<p>When you have successfully connected two (or more) devices, each one will
have a connected {@link android.bluetooth.BluetoothSocket}. This is where the fun
begins because you can share data between devices. Using the {@link
android.bluetooth.BluetoothSocket}, the general procedure to transfer arbitrary data is
-simple:</p>
-<ol>
+simple:</p>
+<ol>
<li>Get the {@link java.io.InputStream} and {@link java.io.OutputStream} that
handle transmissions through the socket, via {@link
android.bluetooth.BluetoothSocket#getInputStream()} and
-{@link android.bluetooth.BluetoothSocket#getOutputStream}, respectively.</li>
-
+{@link android.bluetooth.BluetoothSocket#getOutputStream}, respectively.</li>
+
<li>Read and write data to the streams with {@link
-java.io.InputStream#read(byte[])} and {@link java.io.OutputStream#write(byte[])}.</li>
-</ol>
-
-<p>That's it.</p>
-
+java.io.InputStream#read(byte[])} and {@link java.io.OutputStream#write(byte[])}.</li>
+</ol>
+
+<p>That's it.</p>
+
<p>There are, of course, implementation details to consider. First and foremost,
you should use a dedicated thread for all stream reading and writing. This is
important because both {@link java.io.InputStream#read(byte[])} and {@link
@@ -756,37 +784,37 @@
java.io.InputStream#read(byte[])} quickly enough and the intermediate buffers are full.
So, your main loop in the thread should be dedicated to reading from the {@link
java.io.InputStream}. A separate public method in the thread can be used to initiate
-writes to the {@link java.io.OutputStream}.</p>
-
-<h4>Example</h4>
-
-<p>Here's an example of how this might look:</p>
-<pre>
+writes to the {@link java.io.OutputStream}.</p>
+
+<h4>Example</h4>
+
+<p>Here's an example of how this might look:</p>
+<pre>
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
-
+
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
-
+
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
-
+
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
-
+
public void run() {
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
-
+
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
@@ -800,14 +828,14 @@
}
}
}
-
+
/* Call this from the main Activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) { }
}
-
+
/* Call this from the main Activity to shutdown the connection */
public void cancel() {
try {
@@ -815,27 +843,124 @@
} catch (IOException e) { }
}
}
-</pre>
-
+</pre>
+
<p>The constructor acquires the necessary streams and once executed, the thread
will wait for data to come through the InputStream. When {@link
java.io.InputStream#read(byte[])} returns with
bytes from the stream, the data is sent to the main Activity using a member
Handler from the parent class. Then it goes back and waits for more bytes from
-the stream.</p>
-
+the stream.</p>
+
<p>Sending outgoing data is as simple as calling the thread's
<code>write()</code> method from the main Activity and passing in the bytes to
be sent. This method then simply calls {@link
-java.io.OutputStream#write(byte[])} to send the data to the remote device.</p>
-
+java.io.OutputStream#write(byte[])} to send the data to the remote device.</p>
+
<p>The thread's <code>cancel()</code> method is important so that the connection
can be
terminated at any time by closing the {@link android.bluetooth.BluetoothSocket}.
This should always be called when you're done using the Bluetooth
-connection.</p>
+connection.</p>
+
+<div class="special">
+<p>For a demonstration of using the Bluetooth APIs, see the <a
+href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat sample app</a>.</p>
+</div>
-<div class="special">
-<p>For a complete demonstration using the Bluetooth APIs, see the <a
-href="{@docRoot}resources/samples/BluetoothChat/index.html">Bluetooth Chat sample app</a>.</p>
-</div>
+<h2 id="Profiles">Working with Profiles</h2>
+
+<p>Starting in Android 3.0, the Bluetooth API includes support for working with
+Bluetooth profiles. A <em>Bluetooth profile</em> is a wireless interface
+specification for Bluetooth-based communication between devices. An example
+is the Hands-Free profile. For a mobile phone to connect to a wireless headset,
+both devices must support the Hands-Free profile. </p>
+
+<p>You can implement the interface {@link android.bluetooth.BluetoothProfile} to write
+your own classes to support a particular Bluetooth profile. The Android
+Bluetooth API provides implementations for the following Bluetooth
+profiles:</p>
+<ul>
+
+ <li><strong>Headset</strong>. The Headset profile provides support for
+Bluetooth headsets to be used with mobile phones. Android provides the {@link
+android.bluetooth.BluetoothHeadset} class, which is a proxy for controlling the
+Bluetooth Headset Service via interprocess communication (<a
+href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#IPC">IPC</a
+>). This includes both Bluetooth Headset and Hands-Free (v1.5) profiles. The
+{@link android.bluetooth.BluetoothHeadset} class includes support for AT commands.
+For more discussion of this topic, see <a href="#AT-Commands">Vendor-specific AT commands</a></li>
+
+ <li><strong>A2DP</strong>. The Advanced Audio Distribution Profile (A2DP)
+profile defines how high quality audio can be streamed from one device to
+another over a Bluetooth connection. Android provides the {@link
+android.bluetooth.BluetoothA2dp} class, which is a proxy for controlling
+the Bluetooth A2DP Service via IPC.</li>
+
+</ul>
+
+<p>Here are the basic steps for working with a profile:</p>
+<ol>
+
+ <li>Get the default adapter, as described in <a href="{@docRoot}guide/topics/wireless/bluetooth.
+html#SettingUp">Setting Up Bluetooth</a>.</li>
+
+ <li>Use {@link
+android.bluetooth.BluetoothAdapter#getProfileProxy(android.content.Context,
+android.bluetooth.BluetoothProfile.ServiceListener, int) getProfileProxy()} to
+establish a connection to the profile proxy object associated with the profile.
+In the example below, the profile proxy object is an instance of {@link
+android.bluetooth.BluetoothHeadset}. </li>
+
+ <li>Set up a {@link android.bluetooth.BluetoothProfile.ServiceListener}. This
+listener notifies {@link android.bluetooth.BluetoothProfile} IPC clients when
+they have been connected to or disconnected from the service.</li>
+
+ <li>In {@link
+android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected(int,
+android.bluetooth.BluetoothProfile) onServiceConnected()}, get a handle
+to the profile proxy object.</li>
+
+ <li>Once you have the profile proxy object, you can use it to monitor the
+state of the connection and perform other operations that are relevant to that
+profile.</li>
+</ol>
+<p> For example, this code snippet shows how to connect to a {@link android.bluetooth.BluetoothHeadset} proxy object so that you can control the
+Headset profile:</p>
+
+<pre>BluetoothHeadset mBluetoothHeadset;
+
+// Get the default adapter
+BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+// Establish connection to the proxy.
+mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
+
+private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (profile == BluetoothProfile.HEADSET) {
+ mBluetoothHeadset = (BluetoothHeadset) proxy;
+ }
+ }
+ public void onServiceDisconnected(int profile) {
+ if (profile == BluetoothProfile.HEADSET) {
+ mBluetoothHeadset = null;
+ }
+ }
+};
+
+// ... call functions on mBluetoothHeadset
+
+// Close proxy connection after use.
+mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset);
+</pre>
+
+<h3 id="AT-Commands">Vendor-specific AT commands</h3>
+
+<p>Starting in Android 3.0, applications can register to receive system
+broadcasts of pre-defined vendor-specific AT commands sent by headsets (such as
+a Plantronics +XEVENT command). For example, an application could receive
+broadcasts that indicate a connected device's battery level and could notify the
+user or take other action as needed. Create a broadcast receiver for the {@link
+android.bluetooth.BluetoothHeadset#ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intent
+to handle vendor-specific AT commands for the headset.</p>
diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js
index 097d004..e8c9ae7 100644
--- a/docs/html/resources/resources-data.js
+++ b/docs/html/resources/resources-data.js
@@ -547,6 +547,16 @@
}
},
{
+ tags: ['sample', 'new', 'media' ],
+ path: 'samples/RandomMusicPlayer/index.html',
+ title: {
+ en: 'Random Music Player'
+ },
+ description: {
+ en: 'Demonstrates how to write a multimedia application that plays music from the device and from URLs. It manages media playback from a service and can play music in the background, respecting audio focus changes.'
+ }
+ },
+ {
tags: ['sample', 'new', 'newfeature', 'performance', 'gamedev', 'gl'],
path: 'samples/RenderScript/index.html',
title: {
diff --git a/docs/html/resources/samples/images/randommusicplayer.png b/docs/html/resources/samples/images/randommusicplayer.png
new file mode 100644
index 0000000..e16e067
--- /dev/null
+++ b/docs/html/resources/samples/images/randommusicplayer.png
Binary files differ
diff --git a/docs/html/sdk/sdk_toc.cs b/docs/html/sdk/sdk_toc.cs
index 5750c81..d02c13d 100644
--- a/docs/html/sdk/sdk_toc.cs
+++ b/docs/html/sdk/sdk_toc.cs
@@ -175,7 +175,8 @@
<span style="display:none" class="zh-TW"></span>
</h2>
<ul>
- <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r5c</a>
+ <li><a href="<?cs var:toroot ?>sdk/ndk/index.html">Android NDK, r5c <span
+ class="new">new!</span></a>
</li>
<li><a href="<?cs var:toroot ?>sdk/ndk/overview.html">What is the NDK?</a></li>
</ul>
diff --git a/docs/html/search.jd b/docs/html/search.jd
index 609ade9..339ce2d 100644
--- a/docs/html/search.jd
+++ b/docs/html/search.jd
@@ -1,144 +1,140 @@
page.title=Search Results
@jd:body
-<script src="http://www.google.com/jsapi" type="text/javascript"></script>
+<script src="http://www.google.com/jsapi" type="text/javascript"></script>
<script src="{@docRoot}assets/jquery-history.js" type="text/javascript"></script>
-<script type="text/javascript">
- var tabIndex = 0;
-
- google.load('search', '1');
-
- function OnLoad() {
- document.getElementById("search_autocomplete").style.color = "#000";
-
- // create search control
- searchControl = new google.search.SearchControl();
-
- // use our existing search form and use tabs when multiple searchers are used
- drawOptions = new google.search.DrawOptions();
- drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
- drawOptions.setInput(document.getElementById("search_autocomplete"));
-
- // configure search result options
- searchOptions = new google.search.SearcherOptions();
- searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
-
- // configure each of the searchers, for each tab
- devSiteSearcher = new google.search.WebSearch();
- devSiteSearcher.setUserDefinedLabel("All Developers Site");
- devSiteSearcher.setSiteRestriction("http://developer.android.com/");
-
- devGuideSearcher = new google.search.WebSearch();
- devGuideSearcher.setUserDefinedLabel("Dev Guide");
- devGuideSearcher.setSiteRestriction("http://developer.android.com/guide/");
-
- referenceSearcher = new google.search.WebSearch();
- referenceSearcher.setUserDefinedLabel("Reference");
- referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
-
- blogSearcher = new google.search.WebSearch();
- blogSearcher.setUserDefinedLabel("Blog");
- blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
-
- groupsSearcher = new google.search.WebSearch();
- groupsSearcher.setUserDefinedLabel("Developer Groups");
- groupsSearcher.setSiteRestriction("001283715400630100512:ggqrtvkztwm");
-
- sourceSiteSearcher = new google.search.WebSearch();
- sourceSiteSearcher.setUserDefinedLabel("Android Source");
- sourceSiteSearcher.setSiteRestriction("http://source.android.com");
-
- homeSiteSearcher = new google.search.WebSearch();
- homeSiteSearcher.setUserDefinedLabel("Android Home");
- homeSiteSearcher.setSiteRestriction("http://www.android.com");
-
- // add each searcher to the search control
- searchControl.addSearcher(devSiteSearcher, searchOptions);
- searchControl.addSearcher(devGuideSearcher, searchOptions);
- searchControl.addSearcher(referenceSearcher, searchOptions);
- searchControl.addSearcher(groupsSearcher, searchOptions);
- searchControl.addSearcher(sourceSiteSearcher, searchOptions);
- searchControl.addSearcher(blogSearcher, searchOptions);
-
- // configure result options
- searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
- searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
- searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_LONG);
- searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
-
- // upon ajax search, refresh the url and search title
- searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
- // save the tab index from the hash
- tabIndex = location.hash.split("&t=")[1];
-
- $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
- $.history.add('q=' + query + '&t=' + tabIndex);
- openTab();
- });
-
- // draw the search results box
- searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
-
- // get query and execute the search
- if (location.hash.indexOf("&t=") != -1) {
- searchControl.execute(decodeURI(getQuery(location.hash)));
- }
-
- document.getElementById("search_autocomplete").focus();
- addTabListeners();
- }
- // End of OnLoad
-
-
- google.setOnLoadCallback(OnLoad, true);
-
- // when an event on the browser history occurs (back, forward, load) perform a search
- $(window).history(function(e, hash) {
- var query = decodeURI(getQuery(hash));
- searchControl.execute(query);
-
- $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
- });
-
- // forcefully regain key-up event control (previously jacked by search api)
- $("#search_autocomplete").keyup(function(event) {
- return search_changed(event, false, '/');
- });
-
- // open a tab, specified by its array position
- function openTab() {
- tabIndex = location.hash.split("&t=")[1];
-
- // show the appropriate tab
- var tabHeaders = $(".gsc-tabHeader");
- $(tabHeaders[tabIndex]).click();
- }
-
- // add event listeners to each tab so we can track the browser history
- function addTabListeners() {
- var tabHeaders = $(".gsc-tabHeader");
- for (var i = 0; i < tabHeaders.length; i++) {
- $(tabHeaders[i]).attr("id",i).click(function() {
- var tabHeaders = $(".gsc-tabHeader");
+<script type="text/javascript">
+ var tabIndex = 0;
+
+ google.load('search', '1');
+
+ function OnLoad() {
+ document.getElementById("search_autocomplete").style.color = "#000";
+
+ // create search control
+ searchControl = new google.search.SearchControl();
+
+ // use our existing search form and use tabs when multiple searchers are used
+ drawOptions = new google.search.DrawOptions();
+ drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
+ drawOptions.setInput(document.getElementById("search_autocomplete"));
+
+ // configure search result options
+ searchOptions = new google.search.SearcherOptions();
+ searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
+
+ // configure each of the searchers, for each tab
+ devSiteSearcher = new google.search.WebSearch();
+ devSiteSearcher.setUserDefinedLabel("All");
+ devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
+
+ devGuideSearcher = new google.search.WebSearch();
+ devGuideSearcher.setUserDefinedLabel("Dev Guide");
+ devGuideSearcher.setSiteRestriction("http://developer.android.com/guide/");
+
+ referenceSearcher = new google.search.WebSearch();
+ referenceSearcher.setUserDefinedLabel("Reference");
+ referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
+
+ blogSearcher = new google.search.WebSearch();
+ blogSearcher.setUserDefinedLabel("Blog");
+ blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
+
+ groupsSearcher = new google.search.WebSearch();
+ groupsSearcher.setUserDefinedLabel("Developer Groups");
+ groupsSearcher.setSiteRestriction("001283715400630100512:ggqrtvkztwm");
+
+ sourceSiteSearcher = new google.search.WebSearch();
+ sourceSiteSearcher.setUserDefinedLabel("Android Source");
+ sourceSiteSearcher.setSiteRestriction("http://source.android.com");
+
+ // add each searcher to the search control
+ searchControl.addSearcher(devSiteSearcher, searchOptions);
+ searchControl.addSearcher(devGuideSearcher, searchOptions);
+ searchControl.addSearcher(referenceSearcher, searchOptions);
+ searchControl.addSearcher(groupsSearcher, searchOptions);
+ searchControl.addSearcher(sourceSiteSearcher, searchOptions);
+ searchControl.addSearcher(blogSearcher, searchOptions);
+
+ // configure result options
+ searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
+ searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
+ searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_LONG);
+ searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
+
+ // upon ajax search, refresh the url and search title
+ searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
+ // save the tab index from the hash
+ tabIndex = location.hash.split("&t=")[1];
+
+ $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
+ $.history.add('q=' + query + '&t=' + tabIndex);
+ openTab();
+ });
+
+ // draw the search results box
+ searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
+
+ // get query and execute the search
+ if (location.hash.indexOf("&t=") != -1) {
+ searchControl.execute(decodeURI(getQuery(location.hash)));
+ }
+
+ document.getElementById("search_autocomplete").focus();
+ addTabListeners();
+ }
+ // End of OnLoad
+
+
+ google.setOnLoadCallback(OnLoad, true);
+
+ // when an event on the browser history occurs (back, forward, load) perform a search
+ $(window).history(function(e, hash) {
+ var query = decodeURI(getQuery(hash));
+ searchControl.execute(query);
+
+ $("#searchTitle").html("search results for <em>" + escapeHTML(query) + "</em>");
+ });
+
+ // forcefully regain key-up event control (previously jacked by search api)
+ $("#search_autocomplete").keyup(function(event) {
+ return search_changed(event, false, '/');
+ });
+
+ // open a tab, specified by its array position
+ function openTab() {
+ tabIndex = location.hash.split("&t=")[1];
+
+ // show the appropriate tab
+ var tabHeaders = $(".gsc-tabHeader");
+ $(tabHeaders[tabIndex]).click();
+ }
+
+ // add event listeners to each tab so we can track the browser history
+ function addTabListeners() {
+ var tabHeaders = $(".gsc-tabHeader");
+ for (var i = 0; i < tabHeaders.length; i++) {
+ $(tabHeaders[i]).attr("id",i).click(function() {
+ var tabHeaders = $(".gsc-tabHeader");
var tabIndex = $(this).attr("id");
- $.history.add('q=' + getQuery(location.hash) + '&t=' + tabIndex); // update the hash with the new tab
- });
- }
- }
-
+ $.history.add('q=' + getQuery(location.hash) + '&t=' + tabIndex); // update the hash with the new tab
+ });
+ }
+ }
+
function getQuery(hash) {
- var hashParts = hash.split('&t=');
- var queryParts = hashParts[0].split('=');
- return queryParts[1];
- }
-
- /* returns the given string with all HTML brackets converted to entities
- TODO: move this to the site's JS library */
- function escapeHTML(string) {
- return string.replace(/</g,"<")
- .replace(/>/g,">");
- }
-
+ var hashParts = hash.split('&t=');
+ var queryParts = hashParts[0].split('=');
+ return queryParts[1];
+ }
+
+ /* returns the given string with all HTML brackets converted to entities
+ TODO: move this to the site's JS library */
+ function escapeHTML(string) {
+ return string.replace(/</g,"<")
+ .replace(/>/g,">");
+ }
+
</script>
<div id="mainBodyFixed" style="width:auto; margin:20px">
diff --git a/drm/common/DrmSupportInfo.cpp b/drm/common/DrmSupportInfo.cpp
index 3dee435..5400bdd 100644
--- a/drm/common/DrmSupportInfo.cpp
+++ b/drm/common/DrmSupportInfo.cpp
@@ -43,6 +43,10 @@
}
bool DrmSupportInfo::isSupportedMimeType(const String8& mimeType) const {
+ if (String8("") == mimeType) {
+ return false;
+ }
+
for (unsigned int i = 0; i < mMimeTypeVector.size(); i++) {
const String8 item = mMimeTypeVector.itemAt(i);
diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp
index 458f1b6..2d8e877 100644
--- a/drm/common/IDrmManagerService.cpp
+++ b/drm/common/IDrmManagerService.cpp
@@ -107,6 +107,7 @@
handle->decryptInfo = NULL;
}
handle->copyControlVector.clear();
+ handle->extendedData.clear();
}
int BpDrmManagerService::addUniqueId(int uniqueId) {
diff --git a/drm/java/android/drm/DrmRights.java b/drm/java/android/drm/DrmRights.java
index 5907956..ef9c21d 100755
--- a/drm/java/android/drm/DrmRights.java
+++ b/drm/java/android/drm/DrmRights.java
@@ -107,31 +107,24 @@
/**
* Creates a <code>DrmRights</code> object with the given parameters.
- *<p>
- * The application can pass the processed data as a <code>String</code> or as binary data.
- *<p>
- * The following code snippet shows how to pass the processed data as a <code>String</code>:
- *<p>
- * new DrmRights(data.getBytes(), mimeType)
- *<p>
- * The following code snippet shows how to pass the processed data as binary data:
- *<p>
- * new DrmRights(binaryData[], mimeType)
*
- * @param data A {@link ProcessedData} object.
+ * @param data A {@link ProcessedData} object containing rights information.
+ * data could be null because it's optional for some DRM schemes.
* @param mimeType The MIME type.
*/
public DrmRights(ProcessedData data, String mimeType) {
- mData = data.getData();
+ if (data != null) {
+ mData = data.getData();
- String accountId = data.getAccountId();
- if (null != accountId && !accountId.equals("")) {
- mAccountId = accountId;
- }
+ String accountId = data.getAccountId();
+ if (null != accountId && !accountId.equals("")) {
+ mAccountId = accountId;
+ }
- String subscriptionId = data.getSubscriptionId();
- if (null != subscriptionId && !subscriptionId.equals("")) {
- mSubscriptionId = subscriptionId;
+ String subscriptionId = data.getSubscriptionId();
+ if (null != subscriptionId && !subscriptionId.equals("")) {
+ mSubscriptionId = subscriptionId;
+ }
}
mMimeType = mimeType;
diff --git a/drm/libdrmframework/DrmManagerClientImpl.cpp b/drm/libdrmframework/DrmManagerClientImpl.cpp
index a57dd98..a36bd4a 100644
--- a/drm/libdrmframework/DrmManagerClientImpl.cpp
+++ b/drm/libdrmframework/DrmManagerClientImpl.cpp
@@ -145,7 +145,6 @@
status_t DrmManagerClientImpl::saveRights(int uniqueId, const DrmRights& drmRights,
const String8& rightsPath, const String8& contentPath) {
- status_t status = DRM_ERROR_UNKNOWN;
return getDrmManagerService()->saveRights(
uniqueId, drmRights, rightsPath, contentPath);
}
diff --git a/graphics/java/android/graphics/ParcelSurfaceTexture.java b/graphics/java/android/graphics/ParcelSurfaceTexture.java
index 5272cc6..cc8bd02 100644
--- a/graphics/java/android/graphics/ParcelSurfaceTexture.java
+++ b/graphics/java/android/graphics/ParcelSurfaceTexture.java
@@ -19,6 +19,7 @@
import android.graphics.SurfaceTexture;
import android.os.Parcel;
import android.os.Parcelable;
+import android.view.Surface;
/**
*
@@ -34,6 +35,17 @@
private int mISurfaceTexture;
/**
+ * Create a new ParcelSurfaceTexture from a Surface
+ *
+ * @param surface The Surface to create a ParcelSurfaceTexture from.
+ *
+ * @return Returns a new ParcelSurfaceTexture for the given Surface.
+ */
+ public static ParcelSurfaceTexture fromSurface(Surface surface) {
+ return new ParcelSurfaceTexture(surface);
+ }
+
+ /**
* Create a new ParcelSurfaceTexture from a SurfaceTexture
*
* @param surfaceTexture The SurfaceTexture to transport.
@@ -75,8 +87,11 @@
private ParcelSurfaceTexture(Parcel in) {
nativeReadFromParcel(in);
}
+ private ParcelSurfaceTexture(Surface surface) {
+ nativeInitFromSurface(surface);
+ }
private ParcelSurfaceTexture(SurfaceTexture surfaceTexture) {
- nativeInit(surfaceTexture);
+ nativeInitFromSurfaceTexture(surfaceTexture);
}
@Override
@@ -88,7 +103,8 @@
}
}
- private native void nativeInit(SurfaceTexture surfaceTexture);
+ private native void nativeInitFromSurface(Surface surface);
+ private native void nativeInitFromSurfaceTexture(SurfaceTexture surfaceTexture);
private native void nativeFinalize();
private native void nativeWriteToParcel(Parcel dest, int flags);
private native void nativeReadFromParcel(Parcel in);
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 6c7341f..adb6eac 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -93,6 +93,19 @@
* @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
*/
public SurfaceTexture(int texName) {
+ this(texName, true);
+ }
+
+ /**
+ * Construct a new SurfaceTexture to stream images to a given OpenGL texture.
+ *
+ * @param texName the OpenGL texture object name (e.g. generated via glGenTextures)
+ * @param allowSynchronousMode whether the SurfaceTexture can run in the synchronous mode.
+ * When the image stream comes from OpenGL, SurfaceTexture may run in the synchronous
+ * mode where the producer side may be blocked to avoid skipping frames. To avoid the
+ * thread block, set allowSynchronousMode to false.
+ */
+ public SurfaceTexture(int texName, boolean allowSynchronousMode) {
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(looper);
@@ -101,7 +114,7 @@
} else {
mEventHandler = null;
}
- nativeInit(texName, new WeakReference<SurfaceTexture>(this));
+ nativeInit(texName, new WeakReference<SurfaceTexture>(this), allowSynchronousMode);
}
/**
@@ -131,6 +144,10 @@
*/
public void updateTexImage() {
nativeUpdateTexImage();
+ if (nativeGetQueuedCount() > 0) {
+ Message m = mEventHandler.obtainMessage();
+ mEventHandler.sendMessage(m);
+ }
}
/**
@@ -209,12 +226,13 @@
}
}
- private native void nativeInit(int texName, Object weakSelf);
+ private native void nativeInit(int texName, Object weakSelf, boolean allowSynchronousMode);
private native void nativeFinalize();
private native void nativeGetTransformMatrix(float[] mtx);
private native long nativeGetTimestamp();
private native void nativeSetDefaultBufferSize(int width, int height);
private native void nativeUpdateTexImage();
+ private native int nativeGetQueuedCount();
/*
* We use a class initializer to allow the native code to cache some
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index a4734ff..7e03e1c 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -405,8 +405,11 @@
@Override
public void setAlpha(int alpha) {
- mBitmapState.mPaint.setAlpha(alpha);
- invalidateSelf();
+ int oldAlpha = mBitmapState.mPaint.getAlpha();
+ if (alpha != oldAlpha) {
+ mBitmapState.mPaint.setAlpha(alpha);
+ invalidateSelf();
+ }
}
@Override
diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java
index 9a3ca40..483fa56 100644
--- a/graphics/java/android/graphics/drawable/TransitionDrawable.java
+++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java
@@ -187,8 +187,20 @@
final int alpha = mAlpha;
final boolean crossFade = mCrossFade;
final ChildDrawable[] array = mLayerState.mChildren;
- Drawable d;
+ if (done) {
+ // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw
+ // the appropriate drawable[s] and return
+ if (!crossFade || alpha == 0) {
+ array[0].mDrawable.draw(canvas);
+ }
+ if (alpha == 0xFF) {
+ array[1].mDrawable.draw(canvas);
+ }
+ return;
+ }
+
+ Drawable d;
d = array[0].mDrawable;
if (crossFade) {
d.setAlpha(255 - alpha);
diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java
index 61f2e1f..77dd86a 100644
--- a/graphics/java/android/renderscript/AllocationAdapter.java
+++ b/graphics/java/android/renderscript/AllocationAdapter.java
@@ -27,8 +27,14 @@
*
**/
public class AllocationAdapter extends Allocation {
+ private boolean mConstrainedLOD;
+ private boolean mConstrainedFace;
+ private boolean mConstrainedY;
+ private boolean mConstrainedZ;
+
private int mSelectedDimX;
private int mSelectedDimY;
+ private int mSelectedDimZ;
private int mSelectedCount;
private Allocation mAlloc;
@@ -37,13 +43,14 @@
AllocationAdapter(int id, RenderScript rs, Allocation alloc) {
super(id, rs, null, alloc.mUsage);
- Type t = alloc.getType();
- mSelectedDimX = t.getX();
- mSelectedDimY = t.getY();
- mSelectedCount = t.getCount();
+ mAlloc = alloc;
}
+ int getID() {
+ return mAlloc.getID();
+ }
+
public void copyFrom(BaseObj[] d) {
mRS.validate();
if (d.length != mSelectedCount) {
@@ -54,7 +61,7 @@
for (int ct=0; ct < d.length; ct++) {
i[ct] = d[ct].getID();
}
- subData1D(0, mType.getCount(), i);
+ subData1D(0, mAlloc.mType.getCount(), i);
}
void validateBitmap(Bitmap b) {
@@ -93,7 +100,7 @@
public void subData(int xoff, FieldPacker fp) {
- int eSize = mType.mElement.getSizeBytes();
+ int eSize = mAlloc.mType.mElement.getSizeBytes();
final byte[] data = fp.getData();
int count = data.length / eSize;
@@ -107,7 +114,7 @@
public void subElementData(int xoff, int component_number, FieldPacker fp) {
- if (component_number >= mType.mElement.mElements.length) {
+ if (component_number >= mAlloc.mType.mElement.mElements.length) {
throw new RSIllegalArgumentException("Component_number " + component_number + " out of range.");
}
if(xoff < 0) {
@@ -115,7 +122,7 @@
}
final byte[] data = fp.getData();
- int eSize = mType.mElement.mElements[component_number].getSizeBytes();
+ int eSize = mAlloc.mType.mElement.mElements[component_number].getSizeBytes();
if (data.length != eSize) {
throw new RSIllegalArgumentException("Field packer sizelength " + data.length +
@@ -133,12 +140,13 @@
if(count < 1) {
throw new RSIllegalArgumentException("Count must be >= 1.");
}
- if((off + count) > mSelectedDimX * mSelectedDimY) {
- throw new RSIllegalArgumentException("Overflow, Available count " + mType.getCount() +
+ if((off + count) > mSelectedCount) {
+ throw new RSIllegalArgumentException("Overflow, Available count " + mAlloc.mType.getCount() +
", got " + count + " at offset " + off + ".");
}
if((len) < dataSize) {
- throw new RSIllegalArgumentException("Array too small for allocation type.");
+ throw new RSIllegalArgumentException("Array too small for allocation type. len = " +
+ len + ", dataSize = " + dataSize);
}
}
@@ -223,8 +231,51 @@
mRS.nAllocationRead(getID(), d);
}
+ private void initLOD(int lod) {
+ if (lod < 0) {
+ throw new RSIllegalArgumentException("Attempting to set negative lod (" + lod + ").");
+ }
+
+ int tx = mAlloc.mType.getX();
+ int ty = mAlloc.mType.getY();
+ int tz = mAlloc.mType.getZ();
+
+ for (int ct=0; ct < lod; ct++) {
+ if ((tx==1) && (ty == 1) && (tz == 1)) {
+ throw new RSIllegalArgumentException("Attempting to set lod (" + lod + ") out of range.");
+ }
+
+ if (tx > 1) tx >>= 1;
+ if (ty > 1) ty >>= 1;
+ if (tz > 1) tz >>= 1;
+ }
+
+ mSelectedDimX = tx;
+ mSelectedDimY = ty;
+ mSelectedCount = tx;
+ if (ty > 1) {
+ mSelectedCount *= ty;
+ }
+ if (tz > 1) {
+ mSelectedCount *= tz;
+ }
+ }
+
+ /**
+ * Set the active LOD. The LOD must be within the range for the
+ * type being adapted.
+ *
+ * @param lod The LOD to make active.
+ */
public void setLOD(int lod) {
- mSelectedLOD = lod;
+ if (!mAlloc.getType().hasMipmaps()) {
+ throw new RSInvalidStateException("Cannot set LOD when the allocation type does not include mipmaps.");
+ }
+ if (!mConstrainedLOD) {
+ throw new RSInvalidStateException("Cannot set LOD when the adapter includes mipmaps.");
+ }
+
+ initLOD(lod);
}
public void setFace(Type.CubemapFace cf) {
@@ -245,6 +296,11 @@
static public AllocationAdapter create2D(RenderScript rs, Allocation a) {
rs.validate();
AllocationAdapter aa = new AllocationAdapter(0, rs, a);
+ aa.mConstrainedLOD = true;
+ aa.mConstrainedFace = true;
+ aa.mConstrainedY = false;
+ aa.mConstrainedZ = true;
+ aa.initLOD(0);
return aa;
}
diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java
index fac7144..2739a4b8 100644
--- a/graphics/java/android/renderscript/FieldPacker.java
+++ b/graphics/java/android/renderscript/FieldPacker.java
@@ -165,6 +165,22 @@
addF32(v.w);
}
+ public void addF64(Double2 v) {
+ addF64(v.x);
+ addF64(v.y);
+ }
+ public void addF64(Double3 v) {
+ addF64(v.x);
+ addF64(v.y);
+ addF64(v.z);
+ }
+ public void addF64(Double4 v) {
+ addF64(v.x);
+ addF64(v.y);
+ addF64(v.z);
+ addF64(v.w);
+ }
+
public void addI8(Byte2 v) {
addI8(v.x);
addI8(v.y);
@@ -261,6 +277,38 @@
addU32(v.w);
}
+ public void addI64(Long2 v) {
+ addI64(v.x);
+ addI64(v.y);
+ }
+ public void addI64(Long3 v) {
+ addI64(v.x);
+ addI64(v.y);
+ addI64(v.z);
+ }
+ public void addI64(Long4 v) {
+ addI64(v.x);
+ addI64(v.y);
+ addI64(v.z);
+ addI64(v.w);
+ }
+
+ public void addU64(Long2 v) {
+ addU64(v.x);
+ addU64(v.y);
+ }
+ public void addU64(Long3 v) {
+ addU64(v.x);
+ addU64(v.y);
+ addU64(v.z);
+ }
+ public void addU64(Long4 v) {
+ addU64(v.x);
+ addU64(v.y);
+ addU64(v.z);
+ addU64(v.w);
+ }
+
public void addMatrix(Matrix4f v) {
for (int i=0; i < v.mMat.length; i++) {
addF32(v.mMat[i]);
diff --git a/graphics/java/android/renderscript/Font.java b/graphics/java/android/renderscript/Font.java
index fa27590..616990a 100644
--- a/graphics/java/android/renderscript/Font.java
+++ b/graphics/java/android/renderscript/Font.java
@@ -201,12 +201,14 @@
/**
* Accepts one of the following family names as an argument
- * and will attemp to produce the best match with a system font
+ * and will attempt to produce the best match with a system font:
+ *
* "sans-serif" "arial" "helvetica" "tahoma" "verdana"
* "serif" "times" "times new roman" "palatino" "georgia" "baskerville"
* "goudy" "fantasy" "cursive" "ITC Stone Serif"
* "monospace" "courier" "courier new" "monaco"
- * Returns default font if no match could be found
+ *
+ * Returns default font if no match could be found.
*/
static public Font create(RenderScript rs, Resources res, String familyName, Style fontStyle, float pointSize) {
String fileName = getFontFileName(familyName, fontStyle);
diff --git a/graphics/java/android/renderscript/RSTextureView.java b/graphics/java/android/renderscript/RSTextureView.java
index f63ae8d..6046ee1 100644
--- a/graphics/java/android/renderscript/RSTextureView.java
+++ b/graphics/java/android/renderscript/RSTextureView.java
@@ -94,6 +94,12 @@
}
}
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ //Log.e(RenderScript.LOG_TAG, "onSurfaceTextureUpdated");
+ mSurfaceTexture = surface;
+ }
+
/**
* Inform the view that the activity is paused. The owner of this view must
* call this method when the activity is paused. Calling this method will
diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h
index 317f1e7..fb0b057 100644
--- a/include/android_runtime/android_view_Surface.h
+++ b/include/android_runtime/android_view_Surface.h
@@ -23,10 +23,15 @@
namespace android {
+class Surface;
+
extern sp<ANativeWindow> android_Surface_getNativeWindow(
JNIEnv* env, jobject clazz);
extern bool android_Surface_isInstanceOf(JNIEnv* env, jobject obj);
+/* Gets the underlying Surface from a Surface Java object. */
+extern sp<Surface> Surface_getSurface(JNIEnv* env, jobject thiz);
+
} // namespace android
#endif // _ANDROID_VIEW_SURFACE_H
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 9294df6..e558dfd 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -56,7 +56,7 @@
// tex indicates the name OpenGL texture to which images are to be streamed.
// This texture name cannot be changed once the SurfaceTexture is created.
- SurfaceTexture(GLuint tex);
+ SurfaceTexture(GLuint tex, bool allowSynchronousMode = true);
virtual ~SurfaceTexture();
@@ -361,6 +361,9 @@
// mSynchronousMode whether we're in synchronous mode or not
bool mSynchronousMode;
+ // mAllowSynchronousMode whether we allow synchronous mode or not
+ const bool mAllowSynchronousMode;
+
// mDequeueCondition condition used for dequeueBuffer in synchronous mode
mutable Condition mDequeueCondition;
diff --git a/include/media/Metadata.h b/include/media/Metadata.h
index 9c915ce..07567eb 100644
--- a/include/media/Metadata.h
+++ b/include/media/Metadata.h
@@ -51,47 +51,46 @@
static const Type kAny = 0;
- // Keep in sync with android/media/Metadata.java
- static const Type kTitle = 1; // String
- static const Type kComment = 2; // String
- static const Type kCopyright = 3; // String
- static const Type kAlbum = 4; // String
- static const Type kArtist = 5; // String
- static const Type kAuthor = 6; // String
- static const Type kComposer = 7; // String
- static const Type kGenre = 8; // String
- static const Type kDate = 9; // Date
- static const Type kDuration = 10; // Integer(millisec)
- static const Type kCdTrackNum = 11; // Integer 1-based
- static const Type kCdTrackMax = 12; // Integer
- static const Type kRating = 13; // String
- static const Type kAlbumArt = 14; // byte[]
- static const Type kVideoFrame = 15; // Bitmap
- static const Type kCaption = 16; // TimedText
+ // Playback capabilities.
+ static const Type kPauseAvailable = 1; // Boolean
+ static const Type kSeekBackwardAvailable = 2; // Boolean
+ static const Type kSeekForwardAvailable = 3; // Boolean
+ static const Type kSeekAvailable = 4; // Boolean
- static const Type kBitRate = 17; // Integer, Aggregate rate of
+ // Keep in sync with android/media/Metadata.java
+ static const Type kTitle = 5; // String
+ static const Type kComment = 6; // String
+ static const Type kCopyright = 7; // String
+ static const Type kAlbum = 8; // String
+ static const Type kArtist = 9; // String
+ static const Type kAuthor = 10; // String
+ static const Type kComposer = 11; // String
+ static const Type kGenre = 12; // String
+ static const Type kDate = 13; // Date
+ static const Type kDuration = 14; // Integer(millisec)
+ static const Type kCdTrackNum = 15; // Integer 1-based
+ static const Type kCdTrackMax = 16; // Integer
+ static const Type kRating = 17; // String
+ static const Type kAlbumArt = 18; // byte[]
+ static const Type kVideoFrame = 19; // Bitmap
+
+ static const Type kBitRate = 20; // Integer, Aggregate rate of
// all the streams in bps.
- static const Type kAudioBitRate = 18; // Integer, bps
- static const Type kVideoBitRate = 19; // Integer, bps
- static const Type kAudioSampleRate = 20; // Integer, Hz
- static const Type kVideoframeRate = 21; // Integer, Hz
+ static const Type kAudioBitRate = 21; // Integer, bps
+ static const Type kVideoBitRate = 22; // Integer, bps
+ static const Type kAudioSampleRate = 23; // Integer, Hz
+ static const Type kVideoframeRate = 24; // Integer, Hz
// See RFC2046 and RFC4281.
- static const Type kMimeType = 22; // String
- static const Type kAudioCodec = 23; // String
- static const Type kVideoCodec = 24; // String
+ static const Type kMimeType = 25; // String
+ static const Type kAudioCodec = 26; // String
+ static const Type kVideoCodec = 27; // String
- static const Type kVideoHeight = 25; // Integer
- static const Type kVideoWidth = 26; // Integer
- static const Type kNumTracks = 27; // Integer
- static const Type kDrmCrippled = 28; // Boolean
-
- // Playback capabilities.
- static const Type kPauseAvailable = 29; // Boolean
- static const Type kSeekBackwardAvailable = 30; // Boolean
- static const Type kSeekForwardAvailable = 31; // Boolean
- static const Type kSeekAvailable = 32; // Boolean
+ static const Type kVideoHeight = 28; // Integer
+ static const Type kVideoWidth = 29; // Integer
+ static const Type kNumTracks = 30; // Integer
+ static const Type kDrmCrippled = 31; // Boolean
// @param p[inout] The parcel to append the metadata records
// to. The global metadata header should have been set already.
diff --git a/include/media/stagefright/ColorConverter.h b/include/media/stagefright/ColorConverter.h
index 2ae8a5b..85ba920 100644
--- a/include/media/stagefright/ColorConverter.h
+++ b/include/media/stagefright/ColorConverter.h
@@ -76,6 +76,9 @@
status_t convertYUV420SemiPlanar(
const BitmapParams &src, const BitmapParams &dst);
+ status_t convertTIYUV420PackedSemiPlanar(
+ const BitmapParams &src, const BitmapParams &dst);
+
ColorConverter(const ColorConverter &);
ColorConverter &operator=(const ColorConverter &);
};
diff --git a/include/media/stagefright/MetaData.h b/include/media/stagefright/MetaData.h
index deade5e..99b72ad 100644
--- a/include/media/stagefright/MetaData.h
+++ b/include/media/stagefright/MetaData.h
@@ -118,6 +118,9 @@
// The language code for this media
kKeyMediaLanguage = 'lang', // cstring
+
+ // To store the timed text format data
+ kKeyTextFormatData = 'text', // raw data
};
enum {
diff --git a/include/media/stagefright/openmax/OMX_IVCommon.h b/include/media/stagefright/openmax/OMX_IVCommon.h
index 12b4f93..7ed072b 100644
--- a/include/media/stagefright/openmax/OMX_IVCommon.h
+++ b/include/media/stagefright/openmax/OMX_IVCommon.h
@@ -149,6 +149,7 @@
OMX_COLOR_Format24BitABGR6666,
OMX_COLOR_FormatKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_COLOR_FormatVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
+ OMX_TI_COLOR_FormatYUV420PackedSemiPlanar = 0x7F000100,
OMX_QCOM_COLOR_FormatYVU420SemiPlanar = 0x7FA30C00,
OMX_COLOR_FormatMax = 0x7FFFFFFF
} OMX_COLOR_FORMATTYPE;
diff --git a/include/surfaceflinger/Surface.h b/include/surfaceflinger/Surface.h
index 8845dc9..dc2a845 100644
--- a/include/surfaceflinger/Surface.h
+++ b/include/surfaceflinger/Surface.h
@@ -40,6 +40,7 @@
class GraphicBuffer;
class GraphicBufferMapper;
class IOMX;
+class ISurfaceTexture;
class Rect;
class Surface;
class SurfaceComposerClient;
@@ -154,6 +155,7 @@
bool isValid();
uint32_t getFlags() const { return mFlags; }
uint32_t getIdentity() const { return mIdentity; }
+ sp<ISurfaceTexture> getSurfaceTexture();
// the lock/unlock APIs must be used from the same thread
status_t lock(SurfaceInfo* info, bool blocking = true);
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 4d1d923..9185e1e 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -421,6 +421,10 @@
return NO_ERROR;
}
+sp<ISurfaceTexture> Surface::getSurfaceTexture() {
+ return mSurface != NULL ? mSurface->getSurfaceTexture() : NULL;
+}
+
sp<IBinder> Surface::asBinder() const {
return mSurface!=0 ? mSurface->asBinder() : 0;
}
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index 3cecdb4..37e6d11 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -78,7 +78,7 @@
static void mtxMul(float out[16], const float a[16], const float b[16]);
-SurfaceTexture::SurfaceTexture(GLuint tex) :
+SurfaceTexture::SurfaceTexture(GLuint tex, bool allowSynchronousMode) :
mDefaultWidth(1),
mDefaultHeight(1),
mPixelFormat(PIXEL_FORMAT_RGBA_8888),
@@ -91,7 +91,8 @@
mCurrentTimestamp(0),
mNextTransform(0),
mTexName(tex),
- mSynchronousMode(false) {
+ mSynchronousMode(false),
+ mAllowSynchronousMode(allowSynchronousMode) {
LOGV("SurfaceTexture::SurfaceTexture");
sp<ISurfaceComposer> composer(ComposerService::getComposerService());
mGraphicBufferAlloc = composer->createGraphicBufferAlloc();
@@ -371,6 +372,9 @@
Mutex::Autolock lock(mMutex);
status_t err = OK;
+ if (!mAllowSynchronousMode && enabled)
+ return err;
+
if (!enabled) {
// going to asynchronous mode, drain the queue
while (mSynchronousMode != enabled && !mQueue.isEmpty()) {
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index afab26a..e43f6e5 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -101,8 +101,14 @@
}
mBitmapResources.clear();
+ for (size_t i = 0; i < mFilterResources.size(); i++) {
+ caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i));
+ }
+ mFilterResources.clear();
+
for (size_t i = 0; i < mShaders.size(); i++) {
caches.resourceCache.decrementRefcount(mShaders.itemAt(i));
+ caches.resourceCache.destructor(mShaders.itemAt(i));
}
mShaders.clear();
@@ -151,11 +157,18 @@
caches.resourceCache.incrementRefcount(resource);
}
+ const Vector<SkiaColorFilter*> &filterResources = recorder.getFilterResources();
+ for (size_t i = 0; i < filterResources.size(); i++) {
+ SkiaColorFilter* resource = filterResources.itemAt(i);
+ mFilterResources.add(resource);
+ caches.resourceCache.incrementRefcount(resource);
+ }
+
const Vector<SkiaShader*> &shaders = recorder.getShaders();
for (size_t i = 0; i < shaders.size(); i++) {
- SkiaShader* shader = shaders.itemAt(i);
- mShaders.add(shader);
- caches.resourceCache.incrementRefcount(shader);
+ SkiaShader* resource = shaders.itemAt(i);
+ mShaders.add(resource);
+ caches.resourceCache.incrementRefcount(resource);
}
const Vector<SkPaint*> &paints = recorder.getPaints();
@@ -873,21 +886,27 @@
Caches& caches = Caches::getInstance();
for (size_t i = 0; i < mBitmapResources.size(); i++) {
- SkBitmap* resource = mBitmapResources.itemAt(i);
- caches.resourceCache.decrementRefcount(resource);
+ caches.resourceCache.decrementRefcount(mBitmapResources.itemAt(i));
}
mBitmapResources.clear();
+ for (size_t i = 0; i < mFilterResources.size(); i++) {
+ caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i));
+ }
+ mFilterResources.clear();
+
for (size_t i = 0; i < mShaders.size(); i++) {
- caches.resourceCache.decrementRefcount(mShaders.itemAt(i));
+ caches.resourceCache.decrementRefcount(mShaders.itemAt(i));
}
mShaders.clear();
mShaderMap.clear();
mPaints.clear();
mPaintMap.clear();
+
mPaths.clear();
mPathMap.clear();
+
mMatrices.clear();
}
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index dcf2cf2..b83259f 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -292,6 +292,10 @@
return mBitmapResources;
}
+ const Vector<SkiaColorFilter*>& getFilterResources() const {
+ return mFilterResources;
+ }
+
const Vector<SkiaShader*>& getShaders() const {
return mShaders;
}
@@ -308,10 +312,6 @@
return mMatrices;
}
- const Vector<SkiaColorFilter*>& getFilterResources() const {
- return mFilterResources;
- }
-
private:
void insertRestoreToCount() {
if (mRestoreSaveCount >= 0) {
@@ -419,7 +419,9 @@
inline void addMatrix(SkMatrix* matrix) {
// Copying the matrix is cheap and prevents against the user changing the original
// matrix before the operation that uses it
- addInt((int) new SkMatrix(*matrix));
+ SkMatrix* copy = new SkMatrix(*matrix);
+ addInt((int) copy);
+ mMatrices.add(copy);
}
inline void addBitmap(SkBitmap* bitmap) {
@@ -429,8 +431,7 @@
// which doesn't seem worth the extra cycles for this unlikely case.
addInt((int) bitmap);
mBitmapResources.add(bitmap);
- Caches& caches = Caches::getInstance();
- caches.resourceCache.incrementRefcount(bitmap);
+ Caches::getInstance().resourceCache.incrementRefcount(bitmap);
}
inline void addShader(SkiaShader* shader) {
@@ -454,8 +455,7 @@
inline void addColorFilter(SkiaColorFilter* colorFilter) {
addInt((int) colorFilter);
mFilterResources.add(colorFilter);
- Caches& caches = Caches::getInstance();
- caches.resourceCache.incrementRefcount(colorFilter);
+ Caches::getInstance().resourceCache.incrementRefcount(colorFilter);
}
Vector<SkBitmap*> mBitmapResources;
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 77e63d7..dfb5d3b 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -166,13 +166,13 @@
// Layers management
///////////////////////////////////////////////////////////////////////////////
-Layer* LayerRenderer::createTextureLayer() {
+Layer* LayerRenderer::createTextureLayer(bool isOpaque) {
LAYER_RENDERER_LOGD("Creating new texture layer");
Layer* layer = new Layer(0, 0);
layer->isCacheable = false;
layer->isTextureLayer = true;
- layer->blend = true;
+ layer->blend = !isOpaque;
layer->empty = true;
layer->fbo = 0;
layer->colorFilter = NULL;
@@ -270,8 +270,9 @@
}
void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- GLenum renderTarget, float* transform) {
+ bool isOpaque, GLenum renderTarget, float* transform) {
if (layer) {
+ layer->blend = !isOpaque;
layer->width = width;
layer->height = height;
layer->layer.set(0.0f, 0.0f, width, height);
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
index 797dfc6..2246573 100644
--- a/libs/hwui/LayerRenderer.h
+++ b/libs/hwui/LayerRenderer.h
@@ -55,11 +55,11 @@
Region* getRegion();
GLint getTargetFbo();
- static Layer* createTextureLayer();
+ static Layer* createTextureLayer(bool isOpaque);
static Layer* createLayer(uint32_t width, uint32_t height, bool isOpaque = false);
static bool resizeLayer(Layer* layer, uint32_t width, uint32_t height);
static void updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
- GLenum renderTarget, float* transform);
+ bool isOpaque, GLenum renderTarget, float* transform);
static void destroyLayer(Layer* layer);
static void destroyLayerDeferred(Layer* layer);
static bool copyLayer(Layer* layer, SkBitmap* bitmap);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 88774c6..1c06a0b 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -639,7 +639,7 @@
setupDrawTextureTransform();
setupDrawColor(alpha, alpha, alpha, alpha);
setupDrawColorFilter();
- setupDrawBlending(layer->blend, layer->mode);
+ setupDrawBlending(layer->blend || layer->alpha < 255, layer->mode);
setupDrawProgram();
setupDrawModelView(rect.left, rect.top, rect.right, rect.bottom);
setupDrawPureColorUniforms();
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 9aade51..cd2c405 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -48,9 +48,6 @@
void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) {
Mutex::Autolock _l(mLock);
- for (size_t i = 0; i < mCache->size(); ++i) {
- void* ref = mCache->valueAt(i);
- }
ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
if (ref == NULL || mCache->size() == 0) {
ref = new ResourceReference(resourceType);
@@ -144,7 +141,6 @@
ref->destroyed = true;
if (ref->refCount == 0) {
deleteResourceReference(resource, ref);
- return;
}
}
@@ -162,7 +158,6 @@
ref->destroyed = true;
if (ref->refCount == 0) {
deleteResourceReference(resource, ref);
- return;
}
}
@@ -180,7 +175,6 @@
ref->destroyed = true;
if (ref->refCount == 0) {
deleteResourceReference(resource, ref);
- return;
}
}
@@ -195,7 +189,6 @@
ref->destroyed = true;
if (ref->refCount == 0) {
deleteResourceReference(resource, ref);
- return;
}
}
@@ -209,36 +202,32 @@
}
if (ref->destroyed) {
switch (ref->resourceType) {
- case kBitmap:
- {
- SkBitmap* bitmap = (SkBitmap*)resource;
+ case kBitmap: {
+ SkBitmap* bitmap = (SkBitmap*) resource;
if (Caches::hasInstance()) {
Caches::getInstance().textureCache.removeDeferred(bitmap);
}
delete bitmap;
}
break;
- case kPath:
- {
- SkPath* path = (SkPath*)resource;
+ case kPath: {
+ SkPath* path = (SkPath*) resource;
if (Caches::hasInstance()) {
Caches::getInstance().pathCache.removeDeferred(path);
}
delete path;
}
break;
- case kShader:
- {
- SkiaShader* shader = (SkiaShader*)resource;
+ case kShader: {
+ SkiaShader* shader = (SkiaShader*) resource;
if (Caches::hasInstance()) {
Caches::getInstance().gradientCache.removeDeferred(shader->getSkShader());
}
delete shader;
}
break;
- case kColorFilter:
- {
- SkiaColorFilter* filter = (SkiaColorFilter*)resource;
+ case kColorFilter: {
+ SkiaColorFilter* filter = (SkiaColorFilter*) resource;
delete filter;
}
break;
diff --git a/libs/hwui/SkiaColorFilter.h b/libs/hwui/SkiaColorFilter.h
index bf45e13..1bf475c 100644
--- a/libs/hwui/SkiaColorFilter.h
+++ b/libs/hwui/SkiaColorFilter.h
@@ -59,7 +59,7 @@
return mType;
}
- SkColorFilter *getSkColorFilter() {
+ SkColorFilter* getSkColorFilter() {
return mSkFilter;
}
diff --git a/libs/rs/driver/rsdAllocation.cpp b/libs/rs/driver/rsdAllocation.cpp
index 01a0cf6..17dd931 100644
--- a/libs/rs/driver/rsdAllocation.cpp
+++ b/libs/rs/driver/rsdAllocation.cpp
@@ -460,8 +460,8 @@
uint8_t *srcPtr = getOffsetPtr(srcAlloc, srcXoff, srcYoff + i, srcLod, srcFace);
memcpy(dstPtr, srcPtr, w * elementSize);
- LOGE("COPIED dstXoff(%u), dstYoff(%u), dstLod(%u), dstFace(%u), w(%u), h(%u), srcXoff(%u), srcYoff(%u), srcLod(%u), srcFace(%u)",
- dstXoff, dstYoff, dstLod, dstFace, w, h, srcXoff, srcYoff, srcLod, srcFace);
+ //LOGE("COPIED dstXoff(%u), dstYoff(%u), dstLod(%u), dstFace(%u), w(%u), h(%u), srcXoff(%u), srcYoff(%u), srcLod(%u), srcFace(%u)",
+ // dstXoff, dstYoff, dstLod, dstFace, w, h, srcXoff, srcYoff, srcLod, srcFace);
}
}
diff --git a/libs/rs/driver/rsdRuntimeMath.cpp b/libs/rs/driver/rsdRuntimeMath.cpp
index acb990d..d29da7e 100644
--- a/libs/rs/driver/rsdRuntimeMath.cpp
+++ b/libs/rs/driver/rsdRuntimeMath.cpp
@@ -268,6 +268,87 @@
}
+static int32_t SC_AtomicCas(volatile int32_t *ptr, int32_t expectedValue, int32_t newValue) {
+ int32_t prev;
+
+ do {
+ int32_t ret = android_atomic_release_cas(expectedValue, newValue, ptr);
+ if (!ret) {
+ // The android cas return 0 if it wrote the value. This means the
+ // previous value was the expected value and we can return.
+ return expectedValue;
+ }
+ // We didn't write the value and need to load the "previous" value.
+ prev = *ptr;
+
+ // A race condition exists where the expected value could appear after our cas failed
+ // above. In this case loop until we have a legit previous value or the
+ // write passes.
+ } while (prev == expectedValue);
+ return prev;
+}
+
+
+static int32_t SC_AtomicInc(volatile int32_t *ptr) {
+ return android_atomic_inc(ptr);
+}
+
+static int32_t SC_AtomicDec(volatile int32_t *ptr) {
+ return android_atomic_dec(ptr);
+}
+
+static int32_t SC_AtomicAdd(volatile int32_t *ptr, int32_t value) {
+ return android_atomic_add(value, ptr);
+}
+
+static int32_t SC_AtomicSub(volatile int32_t *ptr, int32_t value) {
+ int32_t prev, status;
+ do {
+ prev = *ptr;
+ status = android_atomic_release_cas(prev, prev - value, ptr);
+ } while (__builtin_expect(status != 0, 0));
+ return prev;
+}
+
+static int32_t SC_AtomicAnd(volatile int32_t *ptr, int32_t value) {
+ return android_atomic_and(value, ptr);
+}
+
+static int32_t SC_AtomicOr(volatile int32_t *ptr, int32_t value) {
+ return android_atomic_or(value, ptr);
+}
+
+static int32_t SC_AtomicXor(volatile int32_t *ptr, int32_t value) {
+ int32_t prev, status;
+ do {
+ prev = *ptr;
+ status = android_atomic_release_cas(prev, prev ^ value, ptr);
+ } while (__builtin_expect(status != 0, 0));
+ return prev;
+}
+
+static int32_t SC_AtomicMin(volatile int32_t *ptr, int32_t value) {
+ int32_t prev, status;
+ do {
+ prev = *ptr;
+ int32_t n = rsMin(value, prev);
+ status = android_atomic_release_cas(prev, n, ptr);
+ } while (__builtin_expect(status != 0, 0));
+ return prev;
+}
+
+static int32_t SC_AtomicMax(volatile int32_t *ptr, int32_t value) {
+ int32_t prev, status;
+ do {
+ prev = *ptr;
+ int32_t n = rsMax(value, prev);
+ status = android_atomic_release_cas(prev, n, ptr);
+ } while (__builtin_expect(status != 0, 0));
+ return prev;
+}
+
+
+
//////////////////////////////////////////////////////////////////////////////
// Class implementation
//////////////////////////////////////////////////////////////////////////////
@@ -425,6 +506,28 @@
{ "_Z6rsRandff", (void *)&SC_randf2, true },
{ "_Z6rsFracf", (void *)&SC_frac, true },
+ // Atomics
+ { "_Z11rsAtomicIncPVi", (void *)&SC_AtomicInc, true },
+ { "_Z11rsAtomicIncPVj", (void *)&SC_AtomicInc, true },
+ { "_Z11rsAtomicDecPVi", (void *)&SC_AtomicDec, true },
+ { "_Z11rsAtomicDecPVj", (void *)&SC_AtomicDec, true },
+ { "_Z11rsAtomicAddPVii", (void *)&SC_AtomicAdd, true },
+ { "_Z11rsAtomicAddPVjj", (void *)&SC_AtomicAdd, true },
+ { "_Z11rsAtomicSubPVii", (void *)&SC_AtomicSub, true },
+ { "_Z11rsAtomicSubPVjj", (void *)&SC_AtomicSub, true },
+ { "_Z11rsAtomicAndPVii", (void *)&SC_AtomicAnd, true },
+ { "_Z11rsAtomicAndPVjj", (void *)&SC_AtomicAnd, true },
+ { "_Z10rsAtomicOrPVii", (void *)&SC_AtomicOr, true },
+ { "_Z10rsAtomicOrPVjj", (void *)&SC_AtomicOr, true },
+ { "_Z11rsAtomicXorPVii", (void *)&SC_AtomicXor, true },
+ { "_Z11rsAtomicXorPVjj", (void *)&SC_AtomicXor, true },
+ { "_Z11rsAtomicMinPVii", (void *)&SC_AtomicMin, true },
+ { "_Z11rsAtomicMinPVjj", (void *)&SC_AtomicMin, true },
+ { "_Z11rsAtomicMaxPVii", (void *)&SC_AtomicMax, true },
+ { "_Z11rsAtomicMaxPVjj", (void *)&SC_AtomicMax, true },
+ { "_Z11rsAtomicCasPViii", (void *)&SC_AtomicCas, true },
+ { "_Z11rsAtomicCasPVjjj", (void *)&SC_AtomicCas, true },
+
{ NULL, NULL, false }
};
diff --git a/libs/rs/driver/rsdRuntimeStubs.cpp b/libs/rs/driver/rsdRuntimeStubs.cpp
index 25302aa..2f9f410 100644
--- a/libs/rs/driver/rsdRuntimeStubs.cpp
+++ b/libs/rs/driver/rsdRuntimeStubs.cpp
@@ -525,10 +525,6 @@
// ::= d # double
static RsdSymbolTable gSyms[] = {
- { "__divsi3", (void *)&SC_divsi3, true },
- { "__modsi3", (void *)&SC_modsi3, true },
- { "__udivsi3", (void *)&SC_udivsi3, true },
- { "__umodsi3", (void *)&SC_umodsi3, true },
{ "memset", (void *)&memset, true },
{ "memcpy", (void *)&memcpy, true },
diff --git a/libs/rs/scriptc/rs_math.rsh b/libs/rs/scriptc/rs_math.rsh
index 584317e..f38f72c 100644
--- a/libs/rs/scriptc/rs_math.rsh
+++ b/libs/rs/scriptc/rs_math.rsh
@@ -258,4 +258,226 @@
rs_allocation output, const void * usrData,
const rs_script_call_t *);
+
+
+/**
+ * Atomic add one to the value at addr.
+ * Equal to rsAtomicAdd(addr, 1)
+ *
+ * @param addr Address of value to increment
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicInc(volatile int32_t* addr);
+/**
+ * Atomic add one to the value at addr.
+ * Equal to rsAtomicAdd(addr, 1)
+ *
+ * @param addr Address of value to increment
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicInc(volatile uint32_t* addr);
+
+/**
+ * Atomic subtract one from the value at addr. Equal to rsAtomicSub(addr, 1)
+ *
+ * @param addr Address of value to decrement
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicDec(volatile int32_t* addr);
+/**
+ * Atomic subtract one from the value at addr. Equal to rsAtomicSub(addr, 1)
+ *
+ * @param addr Address of value to decrement
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicDec(volatile uint32_t* addr);
+
+/**
+ * Atomic add a value to the value at addr. addr[0] += value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to add to the value at addr
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicAdd(volatile int32_t* addr, int32_t value);
+/**
+ * Atomic add a value to the value at addr. addr[0] += value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to add to the value at addr
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicAdd(volatile uint32_t* addr, uint32_t value);
+
+/**
+ * Atomic Subtract a value from the value at addr. addr[0] -= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to subtract from the value at addr
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicSub(volatile int32_t* addr, int32_t value);
+/**
+ * Atomic Subtract a value from the value at addr. addr[0] -= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to subtract from the value at addr
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicSub(volatile uint32_t* addr, uint32_t value);
+
+/**
+ * Atomic Bitwise and a value from the value at addr. addr[0] &= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to and with the value at addr
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicAnd(volatile int32_t* addr, int32_t value);
+/**
+ * Atomic Bitwise and a value from the value at addr. addr[0] &= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to and with the value at addr
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicAnd(volatile uint32_t* addr, uint32_t value);
+
+/**
+ * Atomic Bitwise or a value from the value at addr. addr[0] |= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to or with the value at addr
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicOr(volatile int32_t* addr, int32_t value);
+/**
+ * Atomic Bitwise or a value from the value at addr. addr[0] |= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to or with the value at addr
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicOr(volatile uint32_t* addr, uint32_t value);
+
+/**
+ * Atomic Bitwise xor a value from the value at addr. addr[0] ^= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to xor with the value at addr
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicXor(volatile uint32_t* addr, uint32_t value);
+/**
+ * Atomic Bitwise xor a value from the value at addr. addr[0] ^= value
+ *
+ * @param addr Address of value to modify
+ * @param value Amount to xor with the value at addr
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicXor(volatile int32_t* addr, int32_t value);
+
+/**
+ * Atomic Set the value at addr to the min of addr and value
+ * addr[0] = rsMin(addr[0], value)
+ *
+ * @param addr Address of value to modify
+ * @param value comparison value
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicMin(volatile uint32_t* addr, uint32_t value);
+/**
+ * Atomic Set the value at addr to the min of addr and value
+ * addr[0] = rsMin(addr[0], value)
+ *
+ * @param addr Address of value to modify
+ * @param value comparison value
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicMin(volatile int32_t* addr, int32_t value);
+
+/**
+ * Atomic Set the value at addr to the max of addr and value
+ * addr[0] = rsMax(addr[0], value)
+ *
+ * @param addr Address of value to modify
+ * @param value comparison value
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicMax(volatile uint32_t* addr, uint32_t value);
+/**
+ * Atomic Set the value at addr to the max of addr and value
+ * addr[0] = rsMin(addr[0], value)
+ *
+ * @param addr Address of value to modify
+ * @param value comparison value
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicMax(volatile int32_t* addr, int32_t value);
+
+/**
+ * Compare-and-set operation with a full memory barrier.
+ *
+ * If the value at addr matches compareValue then newValue is written.
+ *
+ * @param addr The address to compare and replace if the compare passes.
+ * @param compareValue The value to test addr[0] against.
+ * @param newValue The value to write if the test passes.
+ *
+ * @return old value
+ */
+extern int32_t __attribute__((overloadable))
+ rsAtomicCas(volatile int32_t* addr, int32_t compareValue, int32_t newValue);
+
+/**
+ * Compare-and-set operation with a full memory barrier.
+ *
+ * If the value at addr matches compareValue then newValue is written.
+ *
+ * @param addr The address to compare and replace if the compare passes.
+ * @param compareValue The value to test addr[0] against.
+ * @param newValue The value to write if the test passes.
+ *
+ * @return old value
+ */
+extern uint32_t __attribute__((overloadable))
+ rsAtomicCas(volatile uint32_t* addr, int32_t compareValue, int32_t newValue);
+
+
#endif
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 2557730..33312d1 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1568,7 +1568,14 @@
return;
case MEDIA_TIMED_TEXT:
if (mOnTimedTextListener != null) {
- mOnTimedTextListener.onTimedText(mMediaPlayer, (String)msg.obj);
+ if (msg.obj == null) {
+ mOnTimedTextListener.onTimedText(mMediaPlayer, null);
+ } else {
+ if (msg.obj instanceof byte[]) {
+ TimedText text = new TimedText((byte[])(msg.obj));
+ mOnTimedTextListener.onTimedText(mMediaPlayer, text);
+ }
+ }
}
return;
@@ -1755,14 +1762,14 @@
public interface OnTimedTextListener
{
/**
- * Called to indicate the video size
+ * Called to indicate an avaliable timed text
*
* @param mp the MediaPlayer associated with this callback
- * @param text the timed text sample which contains the
- * text needed to be displayed.
+ * @param text the timed text sample which contains the text
+ * needed to be displayed and the display format.
* {@hide}
*/
- public void onTimedText(MediaPlayer mp, String text);
+ public void onTimedText(MediaPlayer mp, TimedText text);
}
/**
diff --git a/media/java/android/media/Metadata.java b/media/java/android/media/Metadata.java
index 8d408c2..591a8b9 100644
--- a/media/java/android/media/Metadata.java
+++ b/media/java/android/media/Metadata.java
@@ -32,17 +32,13 @@
Class to hold the media's metadata. Metadata are used
for human consumption and can be embedded in the media (e.g
shoutcast) or available from an external source. The source can be
- local (e.g thumbnail stored in the DB) or remote (e.g caption
- server).
+ local (e.g thumbnail stored in the DB) or remote.
Metadata is like a Bundle. It is sparse and each key can occur at
most once. The key is an integer and the value is the actual metadata.
The caller is expected to know the type of the metadata and call
the right get* method to fetch its value.
-
- // FIXME: unhide.
- {@hide}
*/
public class Metadata
{
@@ -59,69 +55,190 @@
// client to make the data purge-able once it is done with it.
//
+ /**
+ * {@hide}
+ */
public static final int ANY = 0; // Never used for metadata returned, only for filtering.
// Keep in sync with kAny in MediaPlayerService.cpp
+ // Playback capabilities.
+ /**
+ * Indicate whether the media can be paused
+ */
+ public static final int PAUSE_AVAILABLE = 1; // Boolean
+ /**
+ * Indicate whether the media can be backward seeked
+ */
+ public static final int SEEK_BACKWARD_AVAILABLE = 2; // Boolean
+ /**
+ * Indicate whether the media can be forward seeked
+ */
+ public static final int SEEK_FORWARD_AVAILABLE = 3; // Boolean
+ /**
+ * Indicate whether the media can be seeked
+ */
+ public static final int SEEK_AVAILABLE = 4; // Boolean
+
// TODO: Should we use numbers compatible with the metadata retriever?
- public static final int TITLE = 1; // String
- public static final int COMMENT = 2; // String
- public static final int COPYRIGHT = 3; // String
- public static final int ALBUM = 4; // String
- public static final int ARTIST = 5; // String
- public static final int AUTHOR = 6; // String
- public static final int COMPOSER = 7; // String
- public static final int GENRE = 8; // String
- public static final int DATE = 9; // Date
- public static final int DURATION = 10; // Integer(millisec)
- public static final int CD_TRACK_NUM = 11; // Integer 1-based
- public static final int CD_TRACK_MAX = 12; // Integer
- public static final int RATING = 13; // String
- public static final int ALBUM_ART = 14; // byte[]
- public static final int VIDEO_FRAME = 15; // Bitmap
- public static final int CAPTION = 16; // TimedText
+ /**
+ * {@hide}
+ */
+ public static final int TITLE = 5; // String
+ /**
+ * {@hide}
+ */
+ public static final int COMMENT = 6; // String
+ /**
+ * {@hide}
+ */
+ public static final int COPYRIGHT = 7; // String
+ /**
+ * {@hide}
+ */
+ public static final int ALBUM = 8; // String
+ /**
+ * {@hide}
+ */
+ public static final int ARTIST = 9; // String
+ /**
+ * {@hide}
+ */
+ public static final int AUTHOR = 10; // String
+ /**
+ * {@hide}
+ */
+ public static final int COMPOSER = 11; // String
+ /**
+ * {@hide}
+ */
+ public static final int GENRE = 12; // String
+ /**
+ * {@hide}
+ */
+ public static final int DATE = 13; // Date
+ /**
+ * {@hide}
+ */
+ public static final int DURATION = 14; // Integer(millisec)
+ /**
+ * {@hide}
+ */
+ public static final int CD_TRACK_NUM = 15; // Integer 1-based
+ /**
+ * {@hide}
+ */
+ public static final int CD_TRACK_MAX = 16; // Integer
+ /**
+ * {@hide}
+ */
+ public static final int RATING = 17; // String
+ /**
+ * {@hide}
+ */
+ public static final int ALBUM_ART = 18; // byte[]
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_FRAME = 19; // Bitmap
- public static final int BIT_RATE = 17; // Integer, Aggregate rate of
- // all the streams in bps.
+ /**
+ * {@hide}
+ */
+ public static final int BIT_RATE = 20; // Integer, Aggregate rate of
+ // all the streams in bps.
- public static final int AUDIO_BIT_RATE = 18; // Integer, bps
- public static final int VIDEO_BIT_RATE = 19; // Integer, bps
- public static final int AUDIO_SAMPLE_RATE = 20; // Integer, Hz
- public static final int VIDEO_FRAME_RATE = 21; // Integer, Hz
+ /**
+ * {@hide}
+ */
+ public static final int AUDIO_BIT_RATE = 21; // Integer, bps
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_BIT_RATE = 22; // Integer, bps
+ /**
+ * {@hide}
+ */
+ public static final int AUDIO_SAMPLE_RATE = 23; // Integer, Hz
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_FRAME_RATE = 24; // Integer, Hz
// See RFC2046 and RFC4281.
- public static final int MIME_TYPE = 22; // String
- public static final int AUDIO_CODEC = 23; // String
- public static final int VIDEO_CODEC = 24; // String
+ /**
+ * {@hide}
+ */
+ public static final int MIME_TYPE = 25; // String
+ /**
+ * {@hide}
+ */
+ public static final int AUDIO_CODEC = 26; // String
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_CODEC = 27; // String
- public static final int VIDEO_HEIGHT = 25; // Integer
- public static final int VIDEO_WIDTH = 26; // Integer
- public static final int NUM_TRACKS = 27; // Integer
- public static final int DRM_CRIPPLED = 28; // Boolean
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_HEIGHT = 28; // Integer
+ /**
+ * {@hide}
+ */
+ public static final int VIDEO_WIDTH = 29; // Integer
+ /**
+ * {@hide}
+ */
+ public static final int NUM_TRACKS = 30; // Integer
+ /**
+ * {@hide}
+ */
+ public static final int DRM_CRIPPLED = 31; // Boolean
- // Playback capabilities.
- public static final int PAUSE_AVAILABLE = 29; // Boolean
- public static final int SEEK_BACKWARD_AVAILABLE = 30; // Boolean
- public static final int SEEK_FORWARD_AVAILABLE = 31; // Boolean
- public static final int SEEK_AVAILABLE = 32; // Boolean
-
- private static final int LAST_SYSTEM = 32;
+ private static final int LAST_SYSTEM = 31;
private static final int FIRST_CUSTOM = 8192;
// Shorthands to set the MediaPlayer's metadata filter.
+ /**
+ * {@hide}
+ */
public static final Set<Integer> MATCH_NONE = Collections.EMPTY_SET;
+ /**
+ * {@hide}
+ */
public static final Set<Integer> MATCH_ALL = Collections.singleton(ANY);
+ /**
+ * {@hide}
+ */
public static final int STRING_VAL = 1;
+ /**
+ * {@hide}
+ */
public static final int INTEGER_VAL = 2;
+ /**
+ * {@hide}
+ */
public static final int BOOLEAN_VAL = 3;
+ /**
+ * {@hide}
+ */
public static final int LONG_VAL = 4;
+ /**
+ * {@hide}
+ */
public static final int DOUBLE_VAL = 5;
- public static final int TIMED_TEXT_VAL = 6;
- public static final int DATE_VAL = 7;
- public static final int BYTE_ARRAY_VAL = 8;
+ /**
+ * {@hide}
+ */
+ public static final int DATE_VAL = 6;
+ /**
+ * {@hide}
+ */
+ public static final int BYTE_ARRAY_VAL = 7;
// FIXME: misses a type for shared heap is missing (MemoryFile).
// FIXME: misses a type for bitmaps.
- private static final int LAST_TYPE = 8;
+ private static final int LAST_TYPE = 7;
private static final String TAG = "media.Metadata";
private static final int kInt32Size = 4;
@@ -142,28 +259,8 @@
new HashMap<Integer, Integer>();
/**
- * Helper class to hold a triple (time, duration, text). Can be used to
- * implement caption.
+ * {@hide}
*/
- public class TimedText {
- private Date mTime;
- private int mDuration; // millisec
- private String mText;
-
- public TimedText(Date time, int duration, String text) {
- mTime = time;
- mDuration = duration;
- mText = text;
- }
-
- public String toString() {
- StringBuilder res = new StringBuilder(80);
- res.append(mTime).append("-").append(mDuration)
- .append(":").append(mText);
- return res.toString();
- }
- }
-
public Metadata() { }
/**
@@ -273,6 +370,7 @@
* should not modify the parcel after this call (and
* not call recycle on it.)
* @return false if an error occurred.
+ * {@hide}
*/
public boolean parse(Parcel parcel) {
if (parcel.dataAvail() < kMetaHeaderSize) {
@@ -328,36 +426,59 @@
// Caller must make sure the key is present using the {@code has}
// method otherwise a RuntimeException will occur.
+ /**
+ * {@hide}
+ */
public String getString(final int key) {
checkType(key, STRING_VAL);
return mParcel.readString();
}
+ /**
+ * {@hide}
+ */
public int getInt(final int key) {
checkType(key, INTEGER_VAL);
return mParcel.readInt();
}
+ /**
+ * Get the boolean value indicated by key
+ */
public boolean getBoolean(final int key) {
checkType(key, BOOLEAN_VAL);
return mParcel.readInt() == 1;
}
+ /**
+ * {@hide}
+ */
public long getLong(final int key) {
- checkType(key, LONG_VAL);
+ checkType(key, LONG_VAL); /**
+ * {@hide}
+ */
return mParcel.readLong();
}
+ /**
+ * {@hide}
+ */
public double getDouble(final int key) {
checkType(key, DOUBLE_VAL);
return mParcel.readDouble();
}
+ /**
+ * {@hide}
+ */
public byte[] getByteArray(final int key) {
checkType(key, BYTE_ARRAY_VAL);
return mParcel.createByteArray();
}
+ /**
+ * {@hide}
+ */
public Date getDate(final int key) {
checkType(key, DATE_VAL);
final long timeSinceEpoch = mParcel.readLong();
@@ -374,29 +495,30 @@
}
}
- public TimedText getTimedText(final int key) {
- checkType(key, TIMED_TEXT_VAL);
- final Date startTime = new Date(mParcel.readLong()); // epoch
- final int duration = mParcel.readInt(); // millisec
-
- return new TimedText(startTime,
- duration,
- mParcel.readString());
- }
-
- // @return the last available system metadata id. Ids are
- // 1-indexed.
+ /**
+ * @return the last available system metadata id. Ids are
+ * 1-indexed.
+ * {@hide}
+ */
public static int lastSytemId() { return LAST_SYSTEM; }
- // @return the first available cutom metadata id.
+ /**
+ * @return the first available cutom metadata id.
+ * {@hide}
+ */
public static int firstCustomId() { return FIRST_CUSTOM; }
- // @return the last value of known type. Types are 1-indexed.
+ /**
+ * @return the last value of known type. Types are 1-indexed.
+ * {@hide}
+ */
public static int lastType() { return LAST_TYPE; }
- // Check val is either a system id or a custom one.
- // @param val Metadata key to test.
- // @return true if it is in a valid range.
+ /**
+ * Check val is either a system id or a custom one.
+ * @param val Metadata key to test.
+ * @return true if it is in a valid range.
+ **/
private boolean checkMetadataId(final int val) {
if (val <= ANY || (LAST_SYSTEM < val && val < FIRST_CUSTOM)) {
Log.e(TAG, "Invalid metadata ID " + val);
@@ -405,7 +527,9 @@
return true;
}
- // Check the type of the data match what is expected.
+ /**
+ * Check the type of the data match what is expected.
+ */
private void checkType(final int key, final int expectedType) {
final int pos = mKeyToPosMap.get(key);
diff --git a/media/java/android/media/TimedText.java b/media/java/android/media/TimedText.java
new file mode 100644
index 0000000..a055c8b
--- /dev/null
+++ b/media/java/android/media/TimedText.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2011 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;
+
+import android.os.Parcel;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Class to hold the timed text's metadata.
+ *
+ * {@hide}
+ */
+public class TimedText
+{
+ private static final int FIRST_PUBLIC_KEY = 1;
+
+ // These keys must be in sync with the keys in TextDescription.h
+ public static final int KEY_DISPLAY_FLAGS = 1; // int
+ public static final int KEY_STYLE_FLAGS = 2; // int
+ public static final int KEY_BACKGROUND_COLOR_RGBA = 3; // int
+ public static final int KEY_HIGHLIGHT_COLOR_RGBA = 4; // int
+ public static final int KEY_SCROLL_DELAY = 5; // int
+ public static final int KEY_WRAP_TEXT = 6; // int
+ public static final int KEY_START_TIME = 7; // int
+ public static final int KEY_STRUCT_BLINKING_TEXT_LIST = 8; // List<CharPos>
+ public static final int KEY_STRUCT_FONT_LIST = 9; // List<Font>
+ public static final int KEY_STRUCT_HIGHLIGHT_LIST = 10; // List<CharPos>
+ public static final int KEY_STRUCT_HYPER_TEXT_LIST = 11; // List<HyperText>
+ public static final int KEY_STRUCT_KARAOKE_LIST = 12; // List<Karaoke>
+ public static final int KEY_STRUCT_STYLE_LIST = 13; // List<Style>
+ public static final int KEY_STRUCT_TEXT_POS = 14; // TextPos
+ public static final int KEY_STRUCT_JUSTIFICATION = 15; // Justification
+ public static final int KEY_STRUCT_TEXT = 16; // Text
+
+ private static final int LAST_PUBLIC_KEY = 16;
+
+ private static final int FIRST_PRIVATE_KEY = 101;
+
+ // The following keys are used between TimedText.java and
+ // TextDescription.cpp in order to parce the Parcel.
+ private static final int KEY_GLOBAL_SETTING = 101;
+ private static final int KEY_LOCAL_SETTING = 102;
+ private static final int KEY_START_CHAR = 103;
+ private static final int KEY_END_CHAR = 104;
+ private static final int KEY_FONT_ID = 105;
+ private static final int KEY_FONT_SIZE = 106;
+ private static final int KEY_TEXT_COLOR_RGBA = 107;
+
+ private static final int LAST_PRIVATE_KEY = 107;
+
+ private static final String TAG = "TimedText";
+
+ private Parcel mParcel = Parcel.obtain();
+ private final HashMap<Integer, Object> mKeyObjectMap =
+ new HashMap<Integer, Object>();
+
+ private int mDisplayFlags = -1;
+ private int mBackgroundColorRGBA = -1;
+ private int mHighlightColorRGBA = -1;
+ private int mScrollDelay = -1;
+ private int mWrapText = -1;
+
+ private List<CharPos> mBlinkingPosList = null;
+ private List<CharPos> mHighlightPosList = null;
+ private List<Karaoke> mKaraokeList = null;
+ private List<Font> mFontList = null;
+ private List<Style> mStyleList = null;
+ private List<HyperText> mHyperTextList = null;
+
+ private TextPos mTextPos;
+ private Justification mJustification;
+ private Text mTextStruct;
+
+ /**
+ * Helper class to hold the text length and text content of
+ * one text sample. The member variables in this class are
+ * read-only.
+ */
+ public class Text {
+ /**
+ * The byte-count of this text sample
+ */
+ public int textLen;
+
+ /**
+ * The text sample
+ */
+ public byte[] text;
+
+ public Text() { }
+ }
+
+ /**
+ * Helper class to hold the start char offset and end char offset
+ * for Blinking Text or Highlight Text. endChar is the end offset
+ * of the text (startChar + number of characters to be highlighted
+ * or blinked). The member variables in this class are read-only.
+ */
+ public class CharPos {
+ /**
+ * The offset of the start character
+ */
+ public int startChar = -1;
+
+ /**
+ * The offset of the end character
+ */
+ public int endChar = -1;
+
+ public CharPos() { }
+ }
+
+ /**
+ * Helper class to hold the box position to display the text sample.
+ * The member variables in this class are read-only.
+ */
+ public class TextPos {
+ /**
+ * The top position of the text
+ */
+ public int top = -1;
+
+ /**
+ * The left position of the text
+ */
+ public int left = -1;
+
+ /**
+ * The bottom position of the text
+ */
+ public int bottom = -1;
+
+ /**
+ * The right position of the text
+ */
+ public int right = -1;
+
+ public TextPos() { }
+ }
+
+ /**
+ * Helper class to hold the justification for text display in the text box.
+ * The member variables in this class are read-only.
+ */
+ public class Justification {
+ /**
+ * horizontalJustification 0: left, 1: centered, -1: right
+ */
+ public int horizontalJustification = -1;
+
+ /**
+ * verticalJustification 0: top, 1: centered, -1: bottom
+ */
+ public int verticalJustification = -1;
+
+ public Justification() { }
+ }
+
+ /**
+ * Helper class to hold the style information to display the text.
+ * The member variables in this class are read-only.
+ */
+ public class Style {
+ /**
+ * The offset of the start character which applys this style
+ */
+ public int startChar = -1;
+
+ /**
+ * The offset of the end character which applys this style
+ */
+ public int endChar = -1;
+
+ /**
+ * ID of the font. This ID will be used to choose the font
+ * to be used from the font list.
+ */
+ public int fontID = -1;
+
+ /**
+ * True if the characters should be bold
+ */
+ public boolean isBold = false;
+
+ /**
+ * True if the characters should be italic
+ */
+ public boolean isItalic = false;
+
+ /**
+ * True if the characters should be underlined
+ */
+ public boolean isUnderlined = false;
+
+ /**
+ * The size of the font
+ */
+ public int fontSize = -1;
+
+ /**
+ * To specify the RGBA color: 8 bits each of red, green, blue,
+ * and an alpha(transparency) value
+ */
+ public int colorRGBA = -1;
+
+ public Style() { }
+ }
+
+ /**
+ * Helper class to hold the font ID and name.
+ * The member variables in this class are read-only.
+ */
+ public class Font {
+ /**
+ * The font ID
+ */
+ public int ID = -1;
+
+ /**
+ * The font name
+ */
+ public String name;
+
+ public Font() { }
+ }
+
+ /**
+ * Helper class to hold the karaoke information.
+ * The member variables in this class are read-only.
+ */
+ public class Karaoke {
+ /**
+ * The start time (in milliseconds) to highlight the characters
+ * specified by startChar and endChar.
+ */
+ public int startTimeMs = -1;
+
+ /**
+ * The end time (in milliseconds) to highlight the characters
+ * specified by startChar and endChar.
+ */
+ public int endTimeMs = -1;
+
+ /**
+ * The offset of the start character to be highlighted
+ */
+ public int startChar = -1;
+
+ /**
+ * The offset of the end character to be highlighted
+ */
+ public int endChar = -1;
+
+ public Karaoke() { }
+ }
+
+ /**
+ * Helper class to hold the hyper text information.
+ * The member variables in this class are read-only.
+ */
+ public class HyperText {
+ /**
+ * The offset of the start character
+ */
+ public int startChar = -1;
+
+ /**
+ * The offset of the end character
+ */
+ public int endChar = -1;
+
+ /**
+ * The linked-to URL
+ */
+ public String URL;
+
+ /**
+ * The "alt" string for user display
+ */
+ public String altString;
+
+ public HyperText() { }
+ }
+
+ /**
+ * @param obj the byte array which contains the timed text.
+ * @throws IllegalArgumentExcept if parseParcel() fails.
+ * {@hide}
+ */
+ public TimedText(byte[] obj) {
+ mParcel.unmarshall(obj, 0, obj.length);
+
+ if (!parseParcel()) {
+ mKeyObjectMap.clear();
+ throw new IllegalArgumentException("parseParcel() fails");
+ }
+ }
+
+ /**
+ * Go over all the records, collecting metadata keys and fields in the
+ * Parcel. These are stored in mKeyObjectMap for application to retrieve.
+ * @return false if an error occurred during parsing. Otherwise, true.
+ */
+ private boolean parseParcel() {
+ mParcel.setDataPosition(0);
+ if (mParcel.dataAvail() == 0) {
+ return false;
+ }
+
+ int type = mParcel.readInt();
+ if (type == KEY_LOCAL_SETTING) {
+ type = mParcel.readInt();
+ if (type != KEY_START_TIME) {
+ return false;
+ }
+ int mStartTimeMs = mParcel.readInt();
+ mKeyObjectMap.put(type, mStartTimeMs);
+
+ type = mParcel.readInt();
+ if (type != KEY_STRUCT_TEXT) {
+ return false;
+ }
+
+ mTextStruct = new Text();
+ mTextStruct.textLen = mParcel.readInt();
+
+ mTextStruct.text = mParcel.createByteArray();
+ mKeyObjectMap.put(type, mTextStruct);
+
+ } else if (type != KEY_GLOBAL_SETTING) {
+ Log.w(TAG, "Invalid timed text key found: " + type);
+ return false;
+ }
+
+ while (mParcel.dataAvail() > 0) {
+ int key = mParcel.readInt();
+ if (!isValidKey(key)) {
+ Log.w(TAG, "Invalid timed text key found: " + key);
+ return false;
+ }
+
+ Object object = null;
+
+ switch (key) {
+ case KEY_STRUCT_STYLE_LIST: {
+ readStyle();
+ object = mStyleList;
+ break;
+ }
+ case KEY_STRUCT_FONT_LIST: {
+ readFont();
+ object = mFontList;
+ break;
+ }
+ case KEY_STRUCT_HIGHLIGHT_LIST: {
+ readHighlight();
+ object = mHighlightPosList;
+ break;
+ }
+ case KEY_STRUCT_KARAOKE_LIST: {
+ readKaraoke();
+ object = mKaraokeList;
+ break;
+ }
+ case KEY_STRUCT_HYPER_TEXT_LIST: {
+ readHyperText();
+ object = mHyperTextList;
+
+ break;
+ }
+ case KEY_STRUCT_BLINKING_TEXT_LIST: {
+ readBlinkingText();
+ object = mBlinkingPosList;
+
+ break;
+ }
+ case KEY_WRAP_TEXT: {
+ mWrapText = mParcel.readInt();
+ object = mWrapText;
+ break;
+ }
+ case KEY_HIGHLIGHT_COLOR_RGBA: {
+ mHighlightColorRGBA = mParcel.readInt();
+ object = mHighlightColorRGBA;
+ break;
+ }
+ case KEY_DISPLAY_FLAGS: {
+ mDisplayFlags = mParcel.readInt();
+ object = mDisplayFlags;
+ break;
+ }
+ case KEY_STRUCT_JUSTIFICATION: {
+ mJustification = new Justification();
+
+ mJustification.horizontalJustification = mParcel.readInt();
+ mJustification.verticalJustification = mParcel.readInt();
+
+ object = mJustification;
+ break;
+ }
+ case KEY_BACKGROUND_COLOR_RGBA: {
+ mBackgroundColorRGBA = mParcel.readInt();
+ object = mBackgroundColorRGBA;
+ break;
+ }
+ case KEY_STRUCT_TEXT_POS: {
+ mTextPos = new TextPos();
+
+ mTextPos.top = mParcel.readInt();
+ mTextPos.left = mParcel.readInt();
+ mTextPos.bottom = mParcel.readInt();
+ mTextPos.right = mParcel.readInt();
+
+ object = mTextPos;
+ break;
+ }
+ case KEY_SCROLL_DELAY: {
+ mScrollDelay = mParcel.readInt();
+ object = mScrollDelay;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ if (object != null) {
+ if (mKeyObjectMap.containsKey(key)) {
+ mKeyObjectMap.remove(key);
+ }
+ mKeyObjectMap.put(key, object);
+ }
+ }
+
+ mParcel.recycle();
+ return true;
+ }
+
+ /**
+ * To parse and store the Style list.
+ */
+ private void readStyle() {
+ Style style = new Style();
+ boolean endOfStyle = false;
+
+ while (!endOfStyle && (mParcel.dataAvail() > 0)) {
+ int key = mParcel.readInt();
+ switch (key) {
+ case KEY_START_CHAR: {
+ style.startChar = mParcel.readInt();
+ break;
+ }
+ case KEY_END_CHAR: {
+ style.endChar = mParcel.readInt();
+ break;
+ }
+ case KEY_FONT_ID: {
+ style.fontID = mParcel.readInt();
+ break;
+ }
+ case KEY_STYLE_FLAGS: {
+ int flags = mParcel.readInt();
+ // In the absence of any bits set in flags, the text
+ // is plain. Otherwise, 1: bold, 2: italic, 4: underline
+ style.isBold = ((flags % 2) == 1);
+ style.isItalic = ((flags % 4) >= 2);
+ style.isUnderlined = ((flags / 4) == 1);
+ break;
+ }
+ case KEY_FONT_SIZE: {
+ style.fontSize = mParcel.readInt();
+ break;
+ }
+ case KEY_TEXT_COLOR_RGBA: {
+ style.colorRGBA = mParcel.readInt();
+ break;
+ }
+ default: {
+ // End of the Style parsing. Reset the data position back
+ // to the position before the last mParcel.readInt() call.
+ mParcel.setDataPosition(mParcel.dataPosition() - 4);
+ endOfStyle = true;
+ break;
+ }
+ }
+ }
+
+ if (mStyleList == null) {
+ mStyleList = new ArrayList<Style>();
+ }
+ mStyleList.add(style);
+ }
+
+ /**
+ * To parse and store the Font list
+ */
+ private void readFont() {
+ int entryCount = mParcel.readInt();
+
+ for (int i = 0; i < entryCount; i++) {
+ Font font = new Font();
+
+ font.ID = mParcel.readInt();
+ int nameLen = mParcel.readInt();
+
+ byte[] text = mParcel.createByteArray();
+ font.name = new String(text, 0, nameLen);
+
+ if (mFontList == null) {
+ mFontList = new ArrayList<Font>();
+ }
+ mFontList.add(font);
+ }
+ }
+
+ /**
+ * To parse and store the Highlight list
+ */
+ private void readHighlight() {
+ CharPos pos = new CharPos();
+
+ pos.startChar = mParcel.readInt();
+ pos.endChar = mParcel.readInt();
+
+ if (mHighlightPosList == null) {
+ mHighlightPosList = new ArrayList<CharPos>();
+ }
+ mHighlightPosList.add(pos);
+ }
+
+ /**
+ * To parse and store the Karaoke list
+ */
+ private void readKaraoke() {
+ int entryCount = mParcel.readInt();
+
+ for (int i = 0; i < entryCount; i++) {
+ Karaoke kara = new Karaoke();
+
+ kara.startTimeMs = mParcel.readInt();
+ kara.endTimeMs = mParcel.readInt();
+ kara.startChar = mParcel.readInt();
+ kara.endChar = mParcel.readInt();
+
+ if (mKaraokeList == null) {
+ mKaraokeList = new ArrayList<Karaoke>();
+ }
+ mKaraokeList.add(kara);
+ }
+ }
+
+ /**
+ * To parse and store HyperText list
+ */
+ private void readHyperText() {
+ HyperText hyperText = new HyperText();
+
+ hyperText.startChar = mParcel.readInt();
+ hyperText.endChar = mParcel.readInt();
+
+ int len = mParcel.readInt();
+ byte[] url = mParcel.createByteArray();
+ hyperText.URL = new String(url, 0, len);
+
+ len = mParcel.readInt();
+ byte[] alt = mParcel.createByteArray();
+ hyperText.altString = new String(alt, 0, len);
+
+ if (mHyperTextList == null) {
+ mHyperTextList = new ArrayList<HyperText>();
+ }
+ mHyperTextList.add(hyperText);
+ }
+
+ /**
+ * To parse and store blinking text list
+ */
+ private void readBlinkingText() {
+ CharPos blinkingPos = new CharPos();
+
+ blinkingPos.startChar = mParcel.readInt();
+ blinkingPos.endChar = mParcel.readInt();
+
+ if (mBlinkingPosList == null) {
+ mBlinkingPosList = new ArrayList<CharPos>();
+ }
+ mBlinkingPosList.add(blinkingPos);
+ }
+
+ /**
+ * To check whether the given key is valid.
+ * @param key the key to be checked.
+ * @return true if the key is a valid one. Otherwise, false.
+ */
+ public boolean isValidKey(final int key) {
+ if (!((key >= FIRST_PUBLIC_KEY) && (key <= LAST_PUBLIC_KEY))
+ && !((key >= FIRST_PRIVATE_KEY) && (key <= LAST_PRIVATE_KEY))) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * To check whether the given key is contained in this TimedText object.
+ * @param key the key to be checked.
+ * @return true if the key is contained in this TimedText object.
+ * Otherwise, false.
+ */
+ public boolean containsKey(final int key) {
+ if (isValidKey(key) && mKeyObjectMap.containsKey(key)) {
+ return true;
+ }
+ return false;
+ }
+ /**
+ * @return a set of the keys contained in this TimedText object.
+ */
+ public Set keySet() {
+ return mKeyObjectMap.keySet();
+ }
+
+ /**
+ * To retrieve the object associated with the key. Caller must make sure
+ * the key is present using the containsKey method otherwise a
+ * RuntimeException will occur.
+ * @param key the key used to retrieve the object.
+ * @return an object. The object could be an instanceof Integer, List, or
+ * any of the helper classes such as TextPos, Justification, and Text.
+ */
+ public Object getObject(final int key) {
+ if (containsKey(key)) {
+ return mKeyObjectMap.get(key);
+ } else {
+ throw new IllegalArgumentException("Invalid key: " + key);
+ }
+ }
+}
diff --git a/media/java/android/media/videoeditor/VideoEditorImpl.java b/media/java/android/media/videoeditor/VideoEditorImpl.java
index 2105deb..649b98a 100755
--- a/media/java/android/media/videoeditor/VideoEditorImpl.java
+++ b/media/java/android/media/videoeditor/VideoEditorImpl.java
@@ -38,6 +38,7 @@
import android.graphics.Rect;
import android.media.videoeditor.MediaImageItem;
import android.media.videoeditor.MediaItem;
+import android.media.MediaMetadataRetriever;
import android.util.Log;
import android.util.Xml;
import android.view.Surface;
@@ -1833,12 +1834,32 @@
}
Bitmap projectBitmap = null;
- try {
- projectBitmap = mI.getThumbnail(width, height, 500);
- } catch (IllegalArgumentException e) {
- throw new IllegalArgumentException ("Illegal argument error creating project thumbnail");
- } catch (IOException e) {
- throw new IllegalArgumentException ("IO Error creating project thumbnail");
+ String filename = mI.getFilename();
+ if (mI instanceof MediaVideoItem) {
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(filename);
+ Bitmap bitmap = retriever.getFrameAtTime();
+ retriever.release();
+ retriever = null;
+ if (bitmap == null) {
+ String msg = "Thumbnail extraction from " +
+ filename + " failed";
+ throw new IllegalArgumentException(msg);
+ }
+ // Resize the thumbnail to the target size
+ projectBitmap =
+ Bitmap.createScaledBitmap(bitmap, width, height, true);
+ } else {
+ try {
+ projectBitmap = mI.getThumbnail(width, height, 500);
+ } catch (IllegalArgumentException e) {
+ String msg = "Project thumbnail extraction from " +
+ filename + " failed";
+ throw new IllegalArgumentException(msg);
+ } catch (IOException e) {
+ String msg = "IO Error creating project thumbnail";
+ throw new IllegalArgumentException(msg);
+ }
}
try {
diff --git a/media/jni/mediaeditor/VideoEditorClasses.cpp b/media/jni/mediaeditor/VideoEditorClasses.cpp
index 5696433..d43a562 100755
--- a/media/jni/mediaeditor/VideoEditorClasses.cpp
+++ b/media/jni/mediaeditor/VideoEditorClasses.cpp
@@ -144,6 +144,7 @@
VIDEOEDIT_JAVA_CONSTANT_INIT("MP3", M4VIDEOEDITING_kFileType_MP3),
VIDEOEDIT_JAVA_CONSTANT_INIT("PCM", M4VIDEOEDITING_kFileType_PCM),
VIDEOEDIT_JAVA_CONSTANT_INIT("JPG", M4VIDEOEDITING_kFileType_JPG),
+ VIDEOEDIT_JAVA_CONSTANT_INIT("PNG", M4VIDEOEDITING_kFileType_PNG),
VIDEOEDIT_JAVA_CONSTANT_INIT("M4V", M4VIDEOEDITING_kFileType_M4V),
VIDEOEDIT_JAVA_CONSTANT_INIT("UNSUPPORTED", M4VIDEOEDITING_kFileType_Unsupported)
};
@@ -1394,8 +1395,8 @@
pSettings->FileType = (M4VIDEOEDITING_FileType)videoEditJava_getClipTypeJavaToC(
&converted, pEnv->GetIntField(object, fieldIds.fileType));
- if ( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG)
- {
+ if (( pSettings->FileType == M4VIDEOEDITING_kFileType_JPG) ||
+ ( pSettings->FileType == M4VIDEOEDITING_kFileType_PNG)) {
pSettings->FileType = M4VIDEOEDITING_kFileType_ARGB8888;
}
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index a41d7ab..4c4aad0 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -70,7 +70,7 @@
size <<= 1;
if (audio_is_linear_pcm(format)) {
- size /= channelCount * (format == AUDIO_FORMAT_PCM_16_BIT ? 2 : 1);
+ size /= channelCount * audio_bytes_per_sample(format);
}
*frameCount = size;
@@ -258,7 +258,7 @@
int AudioRecord::frameSize() const
{
if (audio_is_linear_pcm(mFormat)) {
- return channelCount()*((format() == AUDIO_FORMAT_PCM_8_BIT) ? sizeof(uint8_t) : sizeof(int16_t));
+ return channelCount()*audio_bytes_per_sample(mFormat);
} else {
return sizeof(uint8_t);
}
@@ -481,7 +481,7 @@
{
AutoMutex lock(mLock);
int active;
- status_t result;
+ status_t result = NO_ERROR;
audio_track_cblk_t* cblk = mCblk;
uint32_t framesReq = audioBuffer->frameCount;
uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 37fe182..31eb97a 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -298,7 +298,7 @@
int AudioTrack::frameSize() const
{
if (audio_is_linear_pcm(mFormat)) {
- return channelCount()*((format() == AUDIO_FORMAT_PCM_8_BIT) ? sizeof(uint8_t) : sizeof(int16_t));
+ return channelCount()*audio_bytes_per_sample(mFormat);
} else {
return sizeof(uint8_t);
}
@@ -314,7 +314,7 @@
void AudioTrack::start()
{
sp<AudioTrackThread> t = mAudioTrackThread;
- status_t status;
+ status_t status = NO_ERROR;
LOGV("start %p", this);
if (t != 0) {
@@ -825,7 +825,7 @@
{
AutoMutex lock(mLock);
int active;
- status_t result;
+ status_t result = NO_ERROR;
audio_track_cblk_t* cblk = mCblk;
uint32_t framesReq = audioBuffer->frameCount;
uint32_t waitTimeMs = (waitCount < 0) ? cblk->bufferTimeoutMs : WAIT_PERIOD_MS;
diff --git a/media/libmedia/Metadata.cpp b/media/libmedia/Metadata.cpp
index aec96f1..8eeebbb 100644
--- a/media/libmedia/Metadata.cpp
+++ b/media/libmedia/Metadata.cpp
@@ -32,7 +32,7 @@
// All these constants below must be kept in sync with Metadata.java.
enum MetadataId {
FIRST_SYSTEM_ID = 1,
- LAST_SYSTEM_ID = 32,
+ LAST_SYSTEM_ID = 31,
FIRST_CUSTOM_ID = 8192
};
@@ -43,7 +43,6 @@
BOOLEAN_VAL,
LONG_VAL,
DOUBLE_VAL,
- TIMED_TEXT_VAL,
DATE_VAL,
BYTE_ARRAY_VAL,
};
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 5fe511f..5582f92 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -889,11 +889,17 @@
uint32_t entry_count = U32_AT(&buffer[4]);
if (entry_count > 1) {
- // For now we only support a single type of media per track.
-
- mLastTrack->skipTrack = true;
- *offset += chunk_size;
- break;
+ // For 3GPP timed text, there could be multiple tx3g boxes contain
+ // multiple text display formats. These formats will be used to
+ // display the timed text.
+ const char *mime;
+ CHECK(mLastTrack->meta->findCString(kKeyMIMEType, &mime));
+ if (strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
+ // For now we only support a single type of media per track.
+ mLastTrack->skipTrack = true;
+ *offset += chunk_size;
+ break;
+ }
}
off64_t stop_offset = *offset + chunk_size;
@@ -1324,9 +1330,53 @@
return parseDrmSINF(offset, data_offset);
}
+ case FOURCC('h', 'd', 'l', 'r'):
+ {
+ uint32_t buffer;
+ if (mDataSource->readAt(
+ data_offset + 8, &buffer, 4) < 4) {
+ return ERROR_IO;
+ }
+
+ uint32_t type = ntohl(buffer);
+ // For the 3GPP file format, the handler-type within the 'hdlr' box
+ // shall be 'text'
+ if (type == FOURCC('t', 'e', 'x', 't')) {
+ mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
case FOURCC('t', 'x', '3', 'g'):
{
- mLastTrack->meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_TEXT_3GPP);
+ uint32_t type;
+ const void *data;
+ size_t size = 0;
+ if (!mLastTrack->meta->findData(
+ kKeyTextFormatData, &type, &data, &size)) {
+ size = 0;
+ }
+
+ uint8_t *buffer = new uint8_t[size + chunk_size];
+
+ if (size > 0) {
+ memcpy(buffer, data, size);
+ }
+
+ if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
+ < chunk_size) {
+ delete[] buffer;
+ buffer = NULL;
+
+ return ERROR_IO;
+ }
+
+ mLastTrack->meta->setData(
+ kKeyTextFormatData, 0, buffer, size + chunk_size);
+
+ delete[] buffer;
*offset += chunk_size;
break;
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 58f6699..f075699d 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -47,10 +47,6 @@
static const uint8_t kNalUnitTypePicParamSet = 0x08;
static const int64_t kInitialDelayTimeUs = 700000LL;
-// Using longer adjustment period to suppress fluctuations in
-// the audio encoding paths
-static const int64_t kVideoMediaTimeAdjustPeriodTimeUs = 600000000LL; // 10 minutes
-
class MPEG4Writer::Track {
public:
Track(MPEG4Writer *owner, const sp<MediaSource> &source, size_t trackId);
@@ -88,8 +84,6 @@
int64_t mTrackDurationUs;
int64_t mMaxChunkDurationUs;
- // For realtime applications, we need to adjust the media clock
- // for video track based on the audio media clock
bool mIsRealTimeRecording;
int64_t mMaxTimeStampUs;
int64_t mEstimatedTrackSizeBytes;
@@ -175,28 +169,9 @@
int64_t mPreviousTrackTimeUs;
int64_t mTrackEveryTimeDurationUs;
- // Has the media time adjustment for video started?
- bool mIsMediaTimeAdjustmentOn;
- // The time stamp when previous media time adjustment period starts
- int64_t mPrevMediaTimeAdjustTimestampUs;
- // Number of vidoe frames whose time stamp may be adjusted
- int64_t mMediaTimeAdjustNumFrames;
- // The sample number when previous meida time adjustmnet period starts
- int64_t mPrevMediaTimeAdjustSample;
- // The total accumulated drift time within a period of
- // kVideoMediaTimeAdjustPeriodTimeUs.
- int64_t mTotalDriftTimeToAdjustUs;
- // The total accumalated drift time since the start of the recording
- // excluding the current time adjustment period
- int64_t mPrevTotalAccumDriftTimeUs;
-
// Update the audio track's drift information.
void updateDriftTime(const sp<MetaData>& meta);
- // Adjust the time stamp of the video track according to
- // the drift time information from the audio track.
- void adjustMediaTime(int64_t *timestampUs);
-
static void *ThreadWrapper(void *me);
status_t threadEntry();
@@ -230,6 +205,8 @@
// Duration is time scale based
void addOneSttsTableEntry(size_t sampleCount, int32_t timescaledDur);
void addOneCttsTableEntry(size_t sampleCount, int32_t timescaledDur);
+
+ bool isTrackMalFormed() const;
void sendTrackSummary(bool hasMultipleTracks);
// Write the boxes
@@ -1512,12 +1489,7 @@
mNumSttsTableEntries = 0;
mNumCttsTableEntries = 0;
mMdatSizeBytes = 0;
- mIsMediaTimeAdjustmentOn = false;
- mPrevMediaTimeAdjustTimestampUs = 0;
- mMediaTimeAdjustNumFrames = 0;
- mPrevMediaTimeAdjustSample = 0;
- mTotalDriftTimeToAdjustUs = 0;
- mPrevTotalAccumDriftTimeUs = 0;
+
mMaxChunkDurationUs = 0;
mHasNegativeCttsDeltaDuration = false;
@@ -1816,128 +1788,6 @@
}
/*
-* The video track's media time adjustment for real-time applications
-* is described as follows:
-*
-* First, the media time adjustment is done for every period of
-* kVideoMediaTimeAdjustPeriodTimeUs. kVideoMediaTimeAdjustPeriodTimeUs
-* is currently a fixed value chosen heuristically. The value of
-* kVideoMediaTimeAdjustPeriodTimeUs should not be very large or very small
-* for two considerations: on one hand, a relatively large value
-* helps reduce large fluctuation of drift time in the audio encoding
-* path; while on the other hand, a relatively small value helps keep
-* restoring synchronization in audio/video more frequently. Note for the
-* very first period of kVideoMediaTimeAdjustPeriodTimeUs, there is
-* no media time adjustment for the video track.
-*
-* Second, the total accumulated audio track time drift found
-* in a period of kVideoMediaTimeAdjustPeriodTimeUs is distributed
-* over a stream of incoming video frames. The number of video frames
-* affected is determined based on the number of recorded video frames
-* within the past kVideoMediaTimeAdjustPeriodTimeUs period.
-* We choose to distribute the drift time over only a portion
-* (rather than all) of the total number of recorded video frames
-* in order to make sure that the video track media time adjustment is
-* completed for the current period before the next video track media
-* time adjustment period starts. Currently, the portion chosen is a
-* half (0.5).
-*
-* Last, various additional checks are performed to ensure that
-* the actual audio encoding path does not have too much drift.
-* In particular, 1) we want to limit the average incremental time
-* adjustment for each video frame to be less than a threshold
-* for a single period of kVideoMediaTimeAdjustPeriodTimeUs.
-* Currently, the threshold is set to 5 ms. If the average incremental
-* media time adjustment for a video frame is larger than the
-* threshold, the audio encoding path has too much time drift.
-* 2) We also want to limit the total time drift in the audio
-* encoding path to be less than a threshold for a period of
-* kVideoMediaTimeAdjustPeriodTimeUs. Currently, the threshold
-* is 0.5% of kVideoMediaTimeAdjustPeriodTimeUs. If the time drift of
-* the audio encoding path is larger than the threshold, the audio
-* encoding path has too much time drift. We treat the large time
-* drift of the audio encoding path as errors, since there is no
-* way to keep audio/video in synchronization for real-time
-* applications if the time drift is too large unless we drop some
-* video frames, which has its own problems that we don't want
-* to get into for the time being.
-*/
-void MPEG4Writer::Track::adjustMediaTime(int64_t *timestampUs) {
- if (*timestampUs - mPrevMediaTimeAdjustTimestampUs >=
- kVideoMediaTimeAdjustPeriodTimeUs) {
-
- LOGV("New media time adjustment period at %lld us", *timestampUs);
- mIsMediaTimeAdjustmentOn = true;
- mMediaTimeAdjustNumFrames =
- (mNumSamples - mPrevMediaTimeAdjustSample) >> 1;
-
- mPrevMediaTimeAdjustTimestampUs = *timestampUs;
- mPrevMediaTimeAdjustSample = mNumSamples;
- int64_t totalAccumDriftTimeUs = mOwner->getDriftTimeUs();
- mTotalDriftTimeToAdjustUs =
- totalAccumDriftTimeUs - mPrevTotalAccumDriftTimeUs;
-
- mPrevTotalAccumDriftTimeUs = totalAccumDriftTimeUs;
-
- // Check on incremental adjusted time per frame
- int64_t adjustTimePerFrameUs =
- mTotalDriftTimeToAdjustUs / mMediaTimeAdjustNumFrames;
-
- if (adjustTimePerFrameUs < 0) {
- adjustTimePerFrameUs = -adjustTimePerFrameUs;
- }
- if (adjustTimePerFrameUs >= 5000) {
- LOGE("Adjusted time per video frame is %lld us",
- adjustTimePerFrameUs);
- CHECK(!"Video frame time adjustment is too large!");
- }
-
- // Check on total accumulated time drift within a period of
- // kVideoMediaTimeAdjustPeriodTimeUs.
- int64_t driftPercentage = (mTotalDriftTimeToAdjustUs * 1000)
- / kVideoMediaTimeAdjustPeriodTimeUs;
-
- if (driftPercentage < 0) {
- driftPercentage = -driftPercentage;
- }
- if (driftPercentage > 5) {
- LOGE("Audio track has time drift %lld us over %lld us",
- mTotalDriftTimeToAdjustUs,
- kVideoMediaTimeAdjustPeriodTimeUs);
-
- CHECK(!"The audio track media time drifts too much!");
- }
-
- }
-
- if (mIsMediaTimeAdjustmentOn) {
- if (mNumSamples - mPrevMediaTimeAdjustSample <=
- mMediaTimeAdjustNumFrames) {
-
- // Do media time incremental adjustment
- int64_t incrementalAdjustTimeUs =
- (mTotalDriftTimeToAdjustUs *
- (mNumSamples - mPrevMediaTimeAdjustSample))
- / mMediaTimeAdjustNumFrames;
-
- *timestampUs +=
- (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs);
-
- LOGV("Incremental video frame media time adjustment: %lld us",
- (incrementalAdjustTimeUs + mPrevTotalAccumDriftTimeUs));
- } else {
- // Within the remaining adjustment period,
- // no incremental adjustment is needed.
- *timestampUs +=
- (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs);
-
- LOGV("Fixed video frame media time adjustment: %lld us",
- (mTotalDriftTimeToAdjustUs + mPrevTotalAccumDriftTimeUs));
- }
- }
-}
-
-/*
* Updates the drift time from the audio track so that
* the video track can get the updated drift time information
* from the file writer. The fluctuation of the drift time of the audio
@@ -2080,32 +1930,6 @@
int32_t isSync = false;
meta_data->findInt32(kKeyIsSyncFrame, &isSync);
-
- /*
- * The original timestamp found in the data buffer will be modified as below:
- *
- * There is a playback offset into this track if the track's start time
- * is not the same as the movie start time, which will be recorded in edst
- * box of the output file. The playback offset is to make sure that the
- * starting time of the audio/video tracks are synchronized. Although the
- * track's media timestamp may be subject to various modifications
- * as outlined below, the track's playback offset time remains unchanged
- * once the first data buffer of the track is received.
- *
- * The media time stamp will be calculated by subtracting the playback offset
- * (and potential pause durations) from the original timestamp in the buffer.
- *
- * If this track is a video track for a real-time recording application with
- * both audio and video tracks, its media timestamp will subject to further
- * modification based on the media clock of the audio track. This modification
- * is needed for the purpose of maintaining good audio/video synchronization.
- *
- * If the recording session is paused and resumed multiple times, the track
- * media timestamp will be modified as if the recording session had never been
- * paused at all during playback of the recorded output file. In other words,
- * the output file will have no memory of pause/resume durations.
- *
- */
CHECK(meta_data->findInt64(kKeyTime, ×tampUs));
////////////////////////////////////////////////////////////////////////////////
@@ -2146,32 +1970,13 @@
timestampUs, cttsDeltaTimeUs);
}
- // Media time adjustment for real-time applications
if (mIsRealTimeRecording) {
if (mIsAudio) {
updateDriftTime(meta_data);
- } else {
- adjustMediaTime(×tampUs);
}
}
CHECK(timestampUs >= 0);
- if (mNumSamples > 1) {
- if (timestampUs <= lastTimestampUs) {
- LOGW("Frame arrives too late!");
- // Don't drop the late frame, since dropping a frame may cause
- // problems later during playback
-
- // The idea here is to avoid having two or more samples with the
- // same timestamp in the output file.
- if (mTimeScale >= 1000000LL) {
- timestampUs = lastTimestampUs + 1;
- } else {
- timestampUs = lastTimestampUs + (1000000LL + (mTimeScale >> 1)) / mTimeScale;
- }
- }
- }
-
LOGV("%s media time stamp: %lld and previous paused duration %lld",
mIsAudio? "Audio": "Video", timestampUs, previousPausedDurationUs);
if (timestampUs > mTrackDurationUs) {
@@ -2278,11 +2083,10 @@
}
- if (mSampleSizes.empty() || // no samples written
- (!mIsAudio && mNumStssTableEntries == 0) || // no sync frames for video
- (OK != checkCodecSpecificData())) { // no codec specific data
+ if (isTrackMalFormed()) {
err = ERROR_MALFORMED;
}
+
mOwner->trackProgressStatus(mTrackId, -1, err);
// Last chunk
@@ -2332,6 +2136,24 @@
return err;
}
+bool MPEG4Writer::Track::isTrackMalFormed() const {
+ if (mSampleSizes.empty()) { // no samples written
+ LOGE("The number of recorded samples is 0");
+ return true;
+ }
+
+ if (!mIsAudio && mNumStssTableEntries == 0) { // no sync frames for video
+ LOGE("There are no sync frames for video track");
+ return true;
+ }
+
+ if (OK != checkCodecSpecificData()) { // no codec specific data
+ return true;
+ }
+
+ return false;
+}
+
void MPEG4Writer::Track::sendTrackSummary(bool hasMultipleTracks) {
// Send track summary only if test mode is enabled.
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index bb8a8be..314425f 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -180,6 +180,7 @@
{ MEDIA_MIMETYPE_AUDIO_G711_ALAW, "G711Decoder" },
{ MEDIA_MIMETYPE_AUDIO_G711_MLAW, "OMX.google.g711.mlaw.decoder" },
{ MEDIA_MIMETYPE_AUDIO_G711_MLAW, "G711Decoder" },
+ { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.DUCATI1.VIDEO.DECODER" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.decode" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.decoder.mpeg4" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.decoder.mpeg4" },
@@ -187,12 +188,14 @@
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Decoder" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.google.mpeg4.decoder" },
{ MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Decoder" },
+ { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.DUCATI1.VIDEO.DECODER" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.decode" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.decoder.h263" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.decoder.h263" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Decoder" },
{ MEDIA_MIMETYPE_VIDEO_H263, "OMX.google.h263.decoder" },
{ MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Decoder" },
+ { MEDIA_MIMETYPE_VIDEO_AVC, "OMX.TI.DUCATI1.VIDEO.DECODER" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.Nvidia.h264.decode" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.7x30.video.decoder.avc" },
{ MEDIA_MIMETYPE_VIDEO_AVC, "OMX.qcom.video.decoder.avc" },
@@ -387,7 +390,10 @@
quirks |= kDefersOutputBufferAllocation;
}
- if (!strncmp(componentName, "OMX.TI.", 7)) {
+ if (!strcmp(componentName, "OMX.TI.DUCATI1.VIDEO.DECODER")) {
+ quirks |= kRequiresAllocateBufferOnInputPorts;
+ quirks |= kRequiresAllocateBufferOnOutputPorts;
+ } else if (!strncmp(componentName, "OMX.TI.", 7)) {
// Apparently I must not use OMX_UseBuffer on either input or
// output ports on any of the TI components or quote:
// "(I) may have unexpected problem (sic) which can be timing related
@@ -1390,6 +1396,7 @@
CHECK(format.eColorFormat == OMX_COLOR_FormatYUV420Planar
|| format.eColorFormat == OMX_COLOR_FormatYUV420SemiPlanar
|| format.eColorFormat == OMX_COLOR_FormatCbYCrY
+ || format.eColorFormat == OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
|| format.eColorFormat == OMX_QCOM_COLOR_FormatYVU420SemiPlanar);
err = mOMX->setParameter(
@@ -3789,7 +3796,9 @@
size_t numNames = sizeof(kNames) / sizeof(kNames[0]);
- if (type == OMX_QCOM_COLOR_FormatYVU420SemiPlanar) {
+ if (type == OMX_TI_COLOR_FormatYUV420PackedSemiPlanar) {
+ return "OMX_TI_COLOR_FormatYUV420PackedSemiPlanar";
+ } else if (type == OMX_QCOM_COLOR_FormatYVU420SemiPlanar) {
return "OMX_QCOM_COLOR_FormatYVU420SemiPlanar";
} else if (type < 0 || (size_t)type >= numNames) {
return "UNKNOWN";
diff --git a/media/libstagefright/colorconversion/ColorConverter.cpp b/media/libstagefright/colorconversion/ColorConverter.cpp
index b28d947..fd933cc 100644
--- a/media/libstagefright/colorconversion/ColorConverter.cpp
+++ b/media/libstagefright/colorconversion/ColorConverter.cpp
@@ -42,6 +42,7 @@
case OMX_COLOR_FormatCbYCrY:
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
case OMX_COLOR_FormatYUV420SemiPlanar:
+ case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
return true;
default:
@@ -113,6 +114,10 @@
err = convertYUV420SemiPlanar(src, dst);
break;
+ case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
+ err = convertTIYUV420PackedSemiPlanar(src, dst);
+ break;
+
default:
{
CHECK(!"Should not be here. Unknown color conversion.");
@@ -417,6 +422,102 @@
return OK;
}
+status_t ColorConverter::convertTIYUV420PackedSemiPlanar(
+ const BitmapParams &src, const BitmapParams &dst) {
+
+/*
+The TIYUV420PackedSemiPlanar format is same as YUV420PackedSemiPlanar but with
+additional padding as shown in the diagram below. The padded width and padded
+height is different for different compression formats and it is read from the
+codec. In this color conversion routine, the padded resolution is obtained from
+src bitmap.
+
+ ------------------------------------
+| |
+| |
+| ------------------------- |
+| | | |
+| | | |
+| | Y DATA | |
+| | | |
+| | | |
+| | | |
+| ------------------------- |
+| |
+| ------------ |
+| | | |
+| | | |
+| | UV DATA | |
+| | | |
+| | | |
+| | | |
+| ------------ |
+| |
+| |
+ ------------------------------------
+*/
+
+ LOGV("src.mCropLeft = %d src.mCropTop =%d src.mWidth = %d src.mHeight = %d"
+ " dst.mWidth = %d dst.mHeight = %d", src.mCropLeft , src.mCropTop,
+ src.mWidth, src.mHeight, dst.mWidth, dst.mHeight);
+
+ size_t offset = (src.mWidth * src.mCropTop) + src.mCropLeft;
+
+ uint8_t *kAdjustedClip = initClip();
+
+ uint32_t *dst_ptr = (uint32_t *)dst.mBits;
+ const uint8_t *src_y = (const uint8_t *)src.mBits;
+ const uint8_t *src_u = (const uint8_t *)(src_y-offset) + (src.mWidth * src.mHeight);
+ src_u += ( ( src.mWidth * (src.mCropTop/2) ) + src.mCropLeft );
+ const uint8_t *src_v = src_u + 1;
+
+ for (size_t y = 0; y < dst.mHeight; ++y) {
+ for (size_t x = 0; x < dst.mWidth; x += 2) {
+
+ signed y1 = (signed)src_y[x] - 16; //Y pixel
+ signed y2 = (signed)src_y[x + 1] - 16; //2nd Y pixel
+
+ signed u = (signed)src_u[x & ~1] - 128; //U component
+ signed v = (signed)src_u[(x & ~1) + 1] - 128; //V component
+
+ signed u_b = u * 517;
+ signed u_g = -u * 100;
+ signed v_g = -v * 208;
+ signed v_r = v * 409;
+
+ signed tmp1 = y1 * 298;
+ signed b1 = (tmp1 + u_b) / 256;
+ signed g1 = (tmp1 + v_g + u_g) / 256;
+ signed r1 = (tmp1 + v_r) / 256;
+
+ signed tmp2 = y2 * 298;
+ signed b2 = (tmp2 + u_b) / 256;
+ signed g2 = (tmp2 + v_g + u_g) / 256;
+ signed r2 = (tmp2 + v_r) / 256;
+
+ uint32_t rgb1 =
+ ((kAdjustedClip[r1] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[b1] >> 3);
+
+ uint32_t rgb2 =
+ ((kAdjustedClip[r2] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[b1] >> 3);
+
+ dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ }
+
+ src_y += src.mWidth; //increment Y-pixel line
+ if (y&1) {
+ src_u += src.mWidth; //increment U-V line
+ }
+
+ dst_ptr += dst.mWidth / 2;
+ }
+ return OK;
+}
+
uint8_t *ColorConverter::initClip() {
static const signed kClipMin = -278;
static const signed kClipMax = 535;
diff --git a/media/libstagefright/include/OMXNodeInstance.h b/media/libstagefright/include/OMXNodeInstance.h
index ca2578f..1ccf50d 100644
--- a/media/libstagefright/include/OMXNodeInstance.h
+++ b/media/libstagefright/include/OMXNodeInstance.h
@@ -109,7 +109,9 @@
void addActiveBuffer(OMX_U32 portIndex, OMX::buffer_id id);
void removeActiveBuffer(OMX_U32 portIndex, OMX::buffer_id id);
void freeActiveBuffers();
-
+ status_t useGraphicBuffer2_l(
+ OMX_U32 portIndex, const sp<GraphicBuffer> &graphicBuffer,
+ OMX::buffer_id *buffer);
static OMX_ERRORTYPE OnEvent(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
diff --git a/media/libstagefright/omx/OMXNodeInstance.cpp b/media/libstagefright/omx/OMXNodeInstance.cpp
index 8462988..12ab941 100644
--- a/media/libstagefright/omx/OMXNodeInstance.cpp
+++ b/media/libstagefright/omx/OMXNodeInstance.cpp
@@ -406,12 +406,77 @@
return OK;
}
+status_t OMXNodeInstance::useGraphicBuffer2_l(
+ OMX_U32 portIndex, const sp<GraphicBuffer>& graphicBuffer,
+ OMX::buffer_id *buffer) {
+
+ // port definition
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 0;
+ def.nVersion.s.nRevision = 0;
+ def.nVersion.s.nStep = 0;
+ def.nPortIndex = portIndex;
+ OMX_ERRORTYPE err = OMX_GetParameter(mHandle, OMX_IndexParamPortDefinition, &def);
+ if (err != OK)
+ {
+ LOGE("%s::%d:Error getting OMX_IndexParamPortDefinition", __FUNCTION__, __LINE__);
+ return err;
+ }
+
+ BufferMeta *bufferMeta = new BufferMeta(graphicBuffer);
+
+ OMX_BUFFERHEADERTYPE *header = NULL;
+ OMX_U8* bufferHandle = const_cast<OMX_U8*>(
+ reinterpret_cast<const OMX_U8*>(graphicBuffer->handle));
+
+ err = OMX_UseBuffer(
+ mHandle,
+ &header,
+ portIndex,
+ bufferMeta,
+ def.nBufferSize,
+ bufferHandle);
+
+ if (err != OMX_ErrorNone) {
+ LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err);
+ delete bufferMeta;
+ bufferMeta = NULL;
+ *buffer = 0;
+ return UNKNOWN_ERROR;
+ }
+
+ CHECK_EQ(header->pBuffer, bufferHandle);
+ CHECK_EQ(header->pAppPrivate, bufferMeta);
+
+ *buffer = header;
+
+ addActiveBuffer(portIndex, *buffer);
+
+ return OK;
+}
+
+// XXX: This function is here for backwards compatibility. Once the OMX
+// implementations have been updated this can be removed and useGraphicBuffer2
+// can be renamed to useGraphicBuffer.
status_t OMXNodeInstance::useGraphicBuffer(
OMX_U32 portIndex, const sp<GraphicBuffer>& graphicBuffer,
OMX::buffer_id *buffer) {
Mutex::Autolock autoLock(mLock);
+ // See if the newer version of the extension is present.
OMX_INDEXTYPE index;
+ if (OMX_GetExtensionIndex(
+ mHandle,
+ const_cast<OMX_STRING>("OMX.google.android.index.useAndroidNativeBuffer2"),
+ &index) == OMX_ErrorNone) {
+ return useGraphicBuffer2_l(portIndex, graphicBuffer, buffer);
+ }
+
+ LOGW("Falling back to the deprecated useAndroidNativeBuffer support. You "
+ "should switch to the useAndroidNativeBuffer2 extension.");
+
OMX_ERRORTYPE err = OMX_GetExtensionIndex(
mHandle,
const_cast<OMX_STRING>("OMX.google.android.index.useAndroidNativeBuffer"),
diff --git a/media/libstagefright/rtsp/ASessionDescription.cpp b/media/libstagefright/rtsp/ASessionDescription.cpp
index f03f7a2..fd0505e 100644
--- a/media/libstagefright/rtsp/ASessionDescription.cpp
+++ b/media/libstagefright/rtsp/ASessionDescription.cpp
@@ -301,6 +301,9 @@
// static
bool ASessionDescription::parseNTPRange(
const char *s, float *npt1, float *npt2) {
+ *npt1 = 0.0f;
+ *npt2 = 0.0f;
+
if (s[0] == '-') {
return false; // no start time available.
}
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index d15d9c5..f89f8e2 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -995,10 +995,12 @@
AString val;
CHECK(GetAttribute(range.c_str(), "npt", &val));
+ bool seekable = true;
+
float npt1, npt2;
if (!ASessionDescription::parseNTPRange(val.c_str(), &npt1, &npt2)) {
// This is a live stream and therefore not seekable.
- return;
+ seekable = false;
}
i = response->mHeaders.indexOfKey("rtp-info");
@@ -1044,7 +1046,7 @@
++n;
}
- mSeekable = true;
+ mSeekable = seekable;
}
sp<APacketSource> getPacketSource(size_t index) {
diff --git a/media/libstagefright/timedtext/Android.mk b/media/libstagefright/timedtext/Android.mk
index 9a6062c..59d0e15 100644
--- a/media/libstagefright/timedtext/Android.mk
+++ b/media/libstagefright/timedtext/Android.mk
@@ -2,6 +2,7 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
+ TextDescriptions.cpp \
TimedTextParser.cpp \
TimedTextPlayer.cpp
diff --git a/media/libstagefright/timedtext/TextDescriptions.cpp b/media/libstagefright/timedtext/TextDescriptions.cpp
new file mode 100644
index 0000000..f9c1fe0
--- /dev/null
+++ b/media/libstagefright/timedtext/TextDescriptions.cpp
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2011 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.
+ */
+
+#include "TextDescriptions.h"
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+TextDescriptions::TextDescriptions() {
+}
+
+status_t TextDescriptions::getParcelOfDescriptions(
+ const uint8_t *data, ssize_t size,
+ uint32_t flags, int timeMs, Parcel *parcel) {
+ parcel->freeData();
+
+ if (flags & IN_BAND_TEXT_3GPP) {
+ if (flags & GLOBAL_DESCRIPTIONS) {
+ return extract3GPPGlobalDescriptions(data, size, parcel, 0);
+ } else if (flags & LOCAL_DESCRIPTIONS) {
+ return extract3GPPLocalDescriptions(data, size, timeMs, parcel, 0);
+ }
+ } else if (flags & OUT_OF_BAND_TEXT_SRT) {
+ if (flags & LOCAL_DESCRIPTIONS) {
+ return extractSRTLocalDescriptions(data, size, timeMs, parcel);
+ }
+ }
+
+ return ERROR_UNSUPPORTED;
+}
+
+// Parse the SRT text sample, and store the timing and text sample in a Parcel.
+// The Parcel will be sent to MediaPlayer.java through event, and will be
+// parsed in TimedText.java.
+status_t TextDescriptions::extractSRTLocalDescriptions(
+ const uint8_t *data, ssize_t size, int timeMs, Parcel *parcel) {
+ parcel->writeInt32(KEY_LOCAL_SETTING);
+ parcel->writeInt32(KEY_START_TIME);
+ parcel->writeInt32(timeMs);
+
+ parcel->writeInt32(KEY_STRUCT_TEXT);
+ // write the size of the text sample
+ parcel->writeInt32(size);
+ // write the text sample as a byte array
+ parcel->writeInt32(size);
+ parcel->write(data, size);
+
+ return OK;
+}
+
+// Extract the local 3GPP display descriptions. 3GPP local descriptions
+// are appended to the text sample if any. The descriptions could include
+// information such as text styles, highlights, karaoke and so on. They
+// are contained in different boxes, such as 'styl' box contains text
+// styles, and 'krok' box contains karaoke timing and positions.
+status_t TextDescriptions::extract3GPPLocalDescriptions(
+ const uint8_t *data, ssize_t size,
+ int timeMs, Parcel *parcel, int depth) {
+ if (depth == 0) {
+ parcel->writeInt32(KEY_LOCAL_SETTING);
+
+ // write start time to display this text sample
+ parcel->writeInt32(KEY_START_TIME);
+ parcel->writeInt32(timeMs);
+
+ ssize_t textLen = (*data) << 8 | (*(data + 1));
+
+ // write text sample length and text sample itself
+ parcel->writeInt32(KEY_STRUCT_TEXT);
+ parcel->writeInt32(textLen);
+ parcel->writeInt32(textLen);
+ parcel->write(data + 2, textLen);
+
+ if (size > textLen) {
+ data += (textLen + 2);
+ size -= (textLen + 2);
+ } else {
+ return OK;
+ }
+ }
+
+ const uint8_t *tmpData = data;
+ ssize_t chunkSize = U32_AT(tmpData);
+ uint32_t chunkType = U32_AT(tmpData + 4);
+
+ if (chunkSize <= 0) {
+ return OK;
+ }
+
+ tmpData += 8;
+
+ switch(chunkType) {
+ // 'styl' box specifies the style of the text.
+ case FOURCC('s', 't', 'y', 'l'):
+ {
+ uint16_t count = U16_AT(tmpData);
+
+ tmpData += 2;
+
+ for (int i = 0; i < count; i++) {
+ parcel->writeInt32(KEY_STRUCT_STYLE_LIST);
+ parcel->writeInt32(KEY_START_CHAR);
+ parcel->writeInt32(U16_AT(tmpData));
+
+ parcel->writeInt32(KEY_END_CHAR);
+ parcel->writeInt32(U16_AT(tmpData + 2));
+
+ parcel->writeInt32(KEY_FONT_ID);
+ parcel->writeInt32(U16_AT(tmpData + 4));
+
+ parcel->writeInt32(KEY_STYLE_FLAGS);
+ parcel->writeInt32(*(tmpData + 6));
+
+ parcel->writeInt32(KEY_FONT_SIZE);
+ parcel->writeInt32(*(tmpData + 7));
+
+ parcel->writeInt32(KEY_TEXT_COLOR_RGBA);
+ uint32_t rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16
+ | *(tmpData + 10) << 8 | *(tmpData + 11);
+ parcel->writeInt32(rgba);
+
+ tmpData += 12;
+ }
+
+ break;
+ }
+ // 'krok' box. The number of highlight events is specified, and each
+ // event is specified by a starting and ending char offset and an end
+ // time for the event.
+ case FOURCC('k', 'r', 'o', 'k'):
+ {
+
+ parcel->writeInt32(KEY_STRUCT_KARAOKE_LIST);
+
+ int startTime = U32_AT(tmpData);
+ uint16_t count = U16_AT(tmpData + 4);
+ parcel->writeInt32(count);
+
+ tmpData += 6;
+ int lastEndTime = 0;
+
+ for (int i = 0; i < count; i++) {
+ parcel->writeInt32(startTime + lastEndTime);
+
+ lastEndTime = U32_AT(tmpData);
+ parcel->writeInt32(lastEndTime);
+
+ parcel->writeInt32(U16_AT(tmpData + 4));
+ parcel->writeInt32(U16_AT(tmpData + 6));
+
+ tmpData += 8;
+ }
+
+ break;
+ }
+ // 'hlit' box specifies highlighted text
+ case FOURCC('h', 'l', 'i', 't'):
+ {
+ parcel->writeInt32(KEY_STRUCT_HIGHLIGHT_LIST);
+
+ // the start char offset to highlight
+ parcel->writeInt32(U16_AT(tmpData));
+ // the last char offset to highlight
+ parcel->writeInt32(U16_AT(tmpData + 2));
+
+ break;
+ }
+ // 'hclr' box specifies the RGBA color: 8 bits each of
+ // red, green, blue, and an alpha(transparency) value
+ case FOURCC('h', 'c', 'l', 'r'):
+ {
+ parcel->writeInt32(KEY_HIGHLIGHT_COLOR_RGBA);
+
+ uint32_t rgba = *(tmpData) << 24 | *(tmpData + 1) << 16
+ | *(tmpData + 2) << 8 | *(tmpData + 3);
+ parcel->writeInt32(rgba);
+
+ break;
+ }
+ // 'dlay' box specifies a delay after a scroll in and/or
+ // before scroll out.
+ case FOURCC('d', 'l', 'a', 'y'):
+ {
+ parcel->writeInt32(KEY_SCROLL_DELAY);
+
+ uint32_t delay = *(tmpData) << 24 | *(tmpData + 1) << 16
+ | *(tmpData + 2) << 8 | *(tmpData + 3);
+ parcel->writeInt32(delay);
+
+ break;
+ }
+ // 'href' box for hyper text link
+ case FOURCC('h', 'r', 'e', 'f'):
+ {
+ parcel->writeInt32(KEY_STRUCT_HYPER_TEXT_LIST);
+
+ // the start offset of the text to be linked
+ parcel->writeInt32(U16_AT(tmpData));
+ // the end offset of the text
+ parcel->writeInt32(U16_AT(tmpData + 2));
+
+ // the number of bytes in the following URL
+ int len = *(tmpData + 4);
+ parcel->writeInt32(len);
+
+ // the linked-to URL
+ parcel->writeInt32(len);
+ parcel->write(tmpData + 5, len);
+
+ tmpData += (5 + len);
+
+ // the number of bytes in the following "alt" string
+ len = *tmpData;
+ parcel->writeInt32(len);
+
+ // an "alt" string for user display
+ parcel->writeInt32(len);
+ parcel->write(tmpData + 1, len);
+
+ break;
+ }
+ // 'tbox' box to indicate the position of the text with values
+ // of top, left, bottom and right
+ case FOURCC('t', 'b', 'o', 'x'):
+ {
+ parcel->writeInt32(KEY_STRUCT_TEXT_POS);
+ parcel->writeInt32(U16_AT(tmpData));
+ parcel->writeInt32(U16_AT(tmpData + 2));
+ parcel->writeInt32(U16_AT(tmpData + 4));
+ parcel->writeInt32(U16_AT(tmpData + 6));
+
+ break;
+ }
+ // 'blnk' to specify the char range to be blinked
+ case FOURCC('b', 'l', 'n', 'k'):
+ {
+ parcel->writeInt32(KEY_STRUCT_BLINKING_TEXT_LIST);
+
+ // start char offset
+ parcel->writeInt32(U16_AT(tmpData));
+ // end char offset
+ parcel->writeInt32(U16_AT(tmpData + 2));
+
+ break;
+ }
+ // 'twrp' box specifies text wrap behavior. If the value if 0x00,
+ // then no wrap. If it's 0x01, then automatic 'soft' wrap is enabled.
+ // 0x02-0xff are reserved.
+ case FOURCC('t', 'w', 'r', 'p'):
+ {
+ parcel->writeInt32(KEY_WRAP_TEXT);
+ parcel->writeInt32(*tmpData);
+
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ if (size > chunkSize) {
+ data += chunkSize;
+ size -= chunkSize;
+ // continue to parse next box
+ return extract3GPPLocalDescriptions(data, size, 0, parcel, 1);
+ }
+
+ return OK;
+}
+
+// To extract box 'tx3g' defined in 3GPP TS 26.245, and store it in a Parcel
+status_t TextDescriptions::extract3GPPGlobalDescriptions(
+ const uint8_t *data, ssize_t size, Parcel *parcel, int depth) {
+
+ ssize_t chunkSize = U32_AT(data);
+ uint32_t chunkType = U32_AT(data + 4);
+ const uint8_t *tmpData = data;
+ tmpData += 8;
+
+ if (size < chunkSize) {
+ return OK;
+ }
+
+ if (depth == 0) {
+ parcel->writeInt32(KEY_GLOBAL_SETTING);
+ }
+ switch(chunkType) {
+ case FOURCC('t', 'x', '3', 'g'):
+ {
+ tmpData += 8; // skip the first 8 bytes
+ parcel->writeInt32(KEY_DISPLAY_FLAGS);
+ parcel->writeInt32(U32_AT(tmpData));
+
+ parcel->writeInt32(KEY_STRUCT_JUSTIFICATION);
+ parcel->writeInt32(tmpData[4]);
+ parcel->writeInt32(tmpData[5]);
+
+ parcel->writeInt32(KEY_BACKGROUND_COLOR_RGBA);
+ uint32_t rgba = *(tmpData + 6) << 24 | *(tmpData + 7) << 16
+ | *(tmpData + 8) << 8 | *(tmpData + 9);
+ parcel->writeInt32(rgba);
+
+ tmpData += 10;
+ parcel->writeInt32(KEY_STRUCT_TEXT_POS);
+ parcel->writeInt32(U16_AT(tmpData));
+ parcel->writeInt32(U16_AT(tmpData + 2));
+ parcel->writeInt32(U16_AT(tmpData + 4));
+ parcel->writeInt32(U16_AT(tmpData + 6));
+
+ tmpData += 8;
+ parcel->writeInt32(KEY_STRUCT_STYLE_LIST);
+ parcel->writeInt32(KEY_START_CHAR);
+ parcel->writeInt32(U16_AT(tmpData));
+
+ parcel->writeInt32(KEY_END_CHAR);
+ parcel->writeInt32(U16_AT(tmpData + 2));
+
+ parcel->writeInt32(KEY_FONT_ID);
+ parcel->writeInt32(U16_AT(tmpData + 4));
+
+ parcel->writeInt32(KEY_STYLE_FLAGS);
+ parcel->writeInt32(*(tmpData + 6));
+
+ parcel->writeInt32(KEY_FONT_SIZE);
+ parcel->writeInt32(*(tmpData + 7));
+
+ parcel->writeInt32(KEY_TEXT_COLOR_RGBA);
+ rgba = *(tmpData + 8) << 24 | *(tmpData + 9) << 16
+ | *(tmpData + 10) << 8 | *(tmpData + 11);
+ parcel->writeInt32(rgba);
+
+ tmpData += 12;
+ parcel->writeInt32(KEY_STRUCT_FONT_LIST);
+ uint16_t count = U16_AT(tmpData);
+ parcel->writeInt32(count);
+
+ tmpData += 2;
+ for (int i = 0; i < count; i++) {
+ // font ID
+ parcel->writeInt32(U16_AT(tmpData));
+
+ // font name length
+ parcel->writeInt32(*(tmpData + 2));
+
+ int len = *(tmpData + 2);
+
+ parcel->write(tmpData + 3, len);
+ tmpData += 3 + len;
+ }
+
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ data += chunkSize;
+ size -= chunkSize;
+
+ if (size > 0) {
+ // continue to extract next 'tx3g'
+ return extract3GPPGlobalDescriptions(data, size, parcel, 1);
+ }
+
+ return OK;
+}
+
+} // namespace android
diff --git a/media/libstagefright/timedtext/TextDescriptions.h b/media/libstagefright/timedtext/TextDescriptions.h
new file mode 100644
index 0000000..01449175
--- /dev/null
+++ b/media/libstagefright/timedtext/TextDescriptions.h
@@ -0,0 +1,84 @@
+ /*
+ * Copyright (C) 2011 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.
+ */
+
+#ifndef TEXT_DESCRIPTIONS_H_
+
+#define TEXT_DESCRIPTIONS_H_
+
+#include <binder/Parcel.h>
+#include <media/stagefright/foundation/ABase.h>
+
+namespace android {
+
+class TextDescriptions {
+public:
+ enum {
+ IN_BAND_TEXT_3GPP = 0x01,
+ OUT_OF_BAND_TEXT_SRT = 0x02,
+
+ GLOBAL_DESCRIPTIONS = 0x100,
+ LOCAL_DESCRIPTIONS = 0x200,
+ };
+
+ static status_t getParcelOfDescriptions(
+ const uint8_t *data, ssize_t size,
+ uint32_t flags, int timeMs, Parcel *parcel);
+private:
+ TextDescriptions();
+
+ enum {
+ // These keys must be in sync with the keys in TimedText.java
+ KEY_DISPLAY_FLAGS = 1, // int
+ KEY_STYLE_FLAGS = 2, // int
+ KEY_BACKGROUND_COLOR_RGBA = 3, // int
+ KEY_HIGHLIGHT_COLOR_RGBA = 4, // int
+ KEY_SCROLL_DELAY = 5, // int
+ KEY_WRAP_TEXT = 6, // int
+ KEY_START_TIME = 7, // int
+ KEY_STRUCT_BLINKING_TEXT_LIST = 8, // List<CharPos>
+ KEY_STRUCT_FONT_LIST = 9, // List<Font>
+ KEY_STRUCT_HIGHLIGHT_LIST = 10, // List<CharPos>
+ KEY_STRUCT_HYPER_TEXT_LIST = 11, // List<HyperText>
+ KEY_STRUCT_KARAOKE_LIST = 12, // List<Karaoke>
+ KEY_STRUCT_STYLE_LIST = 13, // List<Style>
+ KEY_STRUCT_TEXT_POS = 14, // TextPos
+ KEY_STRUCT_JUSTIFICATION = 15, // Justification
+ KEY_STRUCT_TEXT = 16, // Text
+
+ KEY_GLOBAL_SETTING = 101,
+ KEY_LOCAL_SETTING = 102,
+ KEY_START_CHAR = 103,
+ KEY_END_CHAR = 104,
+ KEY_FONT_ID = 105,
+ KEY_FONT_SIZE = 106,
+ KEY_TEXT_COLOR_RGBA = 107,
+ };
+
+ static status_t extractSRTLocalDescriptions(
+ const uint8_t *data, ssize_t size,
+ int timeMs, Parcel *parcel);
+ static status_t extract3GPPGlobalDescriptions(
+ const uint8_t *data, ssize_t size,
+ Parcel *parcel, int depth);
+ static status_t extract3GPPLocalDescriptions(
+ const uint8_t *data, ssize_t size,
+ int timeMs, Parcel *parcel, int depth);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TextDescriptions);
+};
+
+} // namespace android
+#endif // TEXT_DESCRIPTIONS_H_
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.cpp b/media/libstagefright/timedtext/TimedTextPlayer.cpp
index 50bb16d..7c8a747 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.cpp
+++ b/media/libstagefright/timedtext/TimedTextPlayer.cpp
@@ -19,6 +19,7 @@
#include <utils/Log.h>
#include <binder/IPCThreadState.h>
+
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
@@ -27,9 +28,11 @@
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/FileSource.h>
#include <media/stagefright/Utils.h>
+
#include "include/AwesomePlayer.h"
#include "TimedTextPlayer.h"
#include "TimedTextParser.h"
+#include "TextDescriptions.h"
namespace android {
@@ -92,10 +95,11 @@
return BAD_VALUE;
}
+ status_t err;
if (index < mTextTrackVector.size()) { // start an in-band text
mSource = mTextTrackVector.itemAt(index);
- status_t err = mSource->start();
+ err = mSource->start();
if (err != OK) {
return err;
@@ -112,13 +116,17 @@
mTextParser = new TimedTextParser();
}
- status_t err;
if ((err = mTextParser->init(mOutOfBandSource, fileType)) != OK) {
return err;
}
mTextType = kOutOfBandText;
}
+ // send sample description format
+ if ((err = extractAndSendGlobalDescriptions()) != OK) {
+ return err;
+ }
+
int64_t positionUs;
mObserver->getPosition(&positionUs);
seekTo(positionUs);
@@ -211,21 +219,17 @@
}
mTextEventPending = false;
+ if (mData.dataSize() > 0) {
+ notifyListener(MEDIA_TIMED_TEXT, &mData);
+ mData.freeData();
+ }
+
MediaSource::ReadOptions options;
if (mSeeking) {
options.setSeekTo(mSeekTimeUs,
MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC);
mSeeking = false;
- if (mTextType == kInBandText) {
- if (mTextBuffer != NULL) {
- mTextBuffer->release();
- mTextBuffer = NULL;
- }
- } else {
- mText.clear();
- }
-
notifyListener(MEDIA_TIMED_TEXT); //empty text to clear the screen
}
@@ -233,32 +237,12 @@
mObserver->getPosition(&positionUs);
if (mTextType == kInBandText) {
- if (mTextBuffer != NULL) {
- uint8_t *tmp = (uint8_t *)(mTextBuffer->data());
- size_t len = (*tmp) << 8 | (*(tmp + 1));
-
- notifyListener(MEDIA_TIMED_TEXT,
- tmp + 2,
- len);
-
- mTextBuffer->release();
- mTextBuffer = NULL;
-
- }
-
if (mSource->read(&mTextBuffer, &options) != OK) {
return;
}
mTextBuffer->meta_data()->findInt64(kKeyTime, &timeUs);
} else {
- if (mText.size() > 0) {
- notifyListener(MEDIA_TIMED_TEXT,
- mText.c_str(),
- mText.size());
- mText.clear();
- }
-
int64_t endTimeUs;
if (mTextParser->getText(
&mText, &timeUs, &endTimeUs, &options) != OK) {
@@ -266,6 +250,19 @@
}
}
+ if (timeUs > 0) {
+ extractAndAppendLocalDescriptions(timeUs);
+ }
+
+ if (mTextType == kInBandText) {
+ if (mTextBuffer != NULL) {
+ mTextBuffer->release();
+ mTextBuffer = NULL;
+ }
+ } else {
+ mText.clear();
+ }
+
//send the text now
if (timeUs <= positionUs + 100000ll) {
postTextEvent();
@@ -297,7 +294,8 @@
Mutex::Autolock autoLock(mLock);
if (key == KEY_PARAMETER_TIMED_TEXT_ADD_OUT_OF_BAND_SOURCE) {
- String8 uri = request.readString8();
+ const String16 uri16 = request.readString16();
+ String8 uri = String8(uri16);
KeyedVector<String8, String8> headers;
// To support local subtitle file only for now
@@ -327,21 +325,92 @@
return INVALID_OPERATION;
}
-void TimedTextPlayer::notifyListener(
- int msg, const void *data, size_t size) {
+void TimedTextPlayer::notifyListener(int msg, const Parcel *parcel) {
if (mListener != NULL) {
sp<MediaPlayerBase> listener = mListener.promote();
if (listener != NULL) {
- if (size > 0) {
- mData.freeData();
- mData.write(data, size);
-
- listener->sendEvent(msg, 0, 0, &mData);
+ if (parcel && (parcel->dataSize() > 0)) {
+ listener->sendEvent(msg, 0, 0, parcel);
} else { // send an empty timed text to clear the screen
listener->sendEvent(msg);
}
}
}
}
+
+// Each text sample consists of a string of text, optionally with sample
+// modifier description. The modifier description could specify a new
+// text style for the string of text. These descriptions are present only
+// if they are needed. This method is used to extract the modifier
+// description and append it at the end of the text.
+status_t TimedTextPlayer::extractAndAppendLocalDescriptions(int64_t timeUs) {
+ const void *data;
+ size_t size = 0;
+ int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS;
+
+ if (mTextType == kInBandText) {
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ data = mTextBuffer->data();
+ size = mTextBuffer->size();
+ } else {
+ // support 3GPP only for now
+ return ERROR_UNSUPPORTED;
+ }
+ } else {
+ data = mText.c_str();
+ size = mText.size();
+ flag |= TextDescriptions::OUT_OF_BAND_TEXT_SRT;
+ }
+
+ if ((size > 0) && (flag != TextDescriptions::LOCAL_DESCRIPTIONS)) {
+ mData.freeData();
+ return TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, timeUs / 1000, &mData);
+ }
+
+ return OK;
+}
+
+// To extract and send the global text descriptions for all the text samples
+// in the text track or text file.
+status_t TimedTextPlayer::extractAndSendGlobalDescriptions() {
+ const void *data;
+ size_t size = 0;
+ int32_t flag = TextDescriptions::GLOBAL_DESCRIPTIONS;
+
+ if (mTextType == kInBandText) {
+ const char *mime;
+ CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+ // support 3GPP only for now
+ if (!strcasecmp(mime, MEDIA_MIMETYPE_TEXT_3GPP)) {
+ uint32_t type;
+ // get the 'tx3g' box content. This box contains the text descriptions
+ // used to render the text track
+ if (!mSource->getFormat()->findData(
+ kKeyTextFormatData, &type, &data, &size)) {
+ return ERROR_MALFORMED;
+ }
+
+ flag |= TextDescriptions::IN_BAND_TEXT_3GPP;
+ }
+ }
+
+ if ((size > 0) && (flag != TextDescriptions::GLOBAL_DESCRIPTIONS)) {
+ Parcel parcel;
+ if (TextDescriptions::getParcelOfDescriptions(
+ (const uint8_t *)data, size, flag, 0, &parcel) == OK) {
+ if (parcel.dataSize() > 0) {
+ notifyListener(MEDIA_TIMED_TEXT, &parcel);
+ }
+ }
+ }
+
+ return OK;
+}
}
diff --git a/media/libstagefright/timedtext/TimedTextPlayer.h b/media/libstagefright/timedtext/TimedTextPlayer.h
index 590760b..a744db5 100644
--- a/media/libstagefright/timedtext/TimedTextPlayer.h
+++ b/media/libstagefright/timedtext/TimedTextPlayer.h
@@ -103,8 +103,10 @@
void postTextEvent(int64_t delayUs = -1);
void cancelTextEvent();
- void notifyListener(
- int msg, const void *data = NULL, size_t size = 0);
+ void notifyListener(int msg, const Parcel *parcel = NULL);
+
+ status_t extractAndAppendLocalDescriptions(int64_t timeUs);
+ status_t extractAndSendGlobalDescriptions();
DISALLOW_EVIL_CONSTRUCTORS(TimedTextPlayer);
};
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
index 38f598a..bfa3976 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerMetadataParserTest.java
@@ -277,11 +277,17 @@
// getBoolean
@SmallTest
public void testGetBoolean() throws Exception {
- writeBooleanRecord(Metadata.DRM_CRIPPLED, true);
+ writeBooleanRecord(Metadata.PAUSE_AVAILABLE, true);
+ writeBooleanRecord(Metadata.SEEK_AVAILABLE, true);
+ writeBooleanRecord(Metadata.SEEK_BACKWARD_AVAILABLE, true);
+ writeBooleanRecord(Metadata.SEEK_FORWARD_AVAILABLE, true);
adjustSize();
assertParse();
- assertEquals(true, mMetadata.getBoolean(Metadata.DRM_CRIPPLED));
+ assertEquals(true, mMetadata.getBoolean(Metadata.PAUSE_AVAILABLE));
+ assertEquals(true, mMetadata.getBoolean(Metadata.SEEK_AVAILABLE));
+ assertEquals(true, mMetadata.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE));
+ assertEquals(true, mMetadata.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE));
}
// getLong
@@ -329,19 +335,6 @@
assertEquals(new Date(0), mMetadata.getDate(Metadata.DATE));
}
- // getTimedText
- @SmallTest
- public void testGetTimedText() throws Exception {
- Date now = Calendar.getInstance().getTime();
- writeTimedTextRecord(Metadata.CAPTION, now.getTime(),
- 10, "Some caption");
- adjustSize();
- assertParse();
-
- Metadata.TimedText caption = mMetadata.getTimedText(Metadata.CAPTION);
- assertEquals("" + now + "-" + 10 + ":Some caption", caption.toString());
- }
-
// ----------------------------------------------------------------------
// HELPERS TO APPEND RECORDS
// ----------------------------------------------------------------------
@@ -416,17 +409,4 @@
mParcel.writeString(tz);
adjustSize(start);
}
-
- // Insert a TimedText record at the current position.
- private void writeTimedTextRecord(int metadataId, long begin,
- int duration, String text) {
- final int start = mParcel.dataPosition();
- mParcel.writeInt(-1); // Placeholder for the length
- mParcel.writeInt(metadataId);
- mParcel.writeInt(Metadata.TIMED_TEXT_VAL);
- mParcel.writeLong(begin);
- mParcel.writeInt(duration);
- mParcel.writeString(text);
- adjustSize(start);
- }
}
diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
index 7775dbf..e0c38b1 100644
--- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
+++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java
@@ -18,7 +18,6 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.nfc.ApduList;
import android.nfc.INfcAdapterExtras;
import android.nfc.NfcAdapter;
import android.os.RemoteException;
@@ -207,4 +206,21 @@
public NfcExecutionEnvironment getEmbeddedExecutionEnvironment() {
return sEmbeddedEe;
}
+
+ /**
+ * Authenticate the client application.
+ *
+ * Some implementations of NFC Adapter Extras may require applications
+ * to authenticate with a token, before using other methods.
+ *
+ * @param a implementation specific token
+ * @throws a {@link java.lang.SecurityException} if authentication failed
+ */
+ public void authenticate(byte[] token) {
+ try {
+ sService.authenticate(token);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 93f7af3..fe279c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -1040,7 +1040,7 @@
}
});
- WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG,
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java
index 187a486..60906a1 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPreferenceActivity.java
@@ -1,93 +1,96 @@
-/*
- * Copyright (C) 2011 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.systemui.usb;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.hardware.usb.UsbManager;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.util.Log;
-import android.widget.Button;
-
-import java.io.File;
-
-import com.android.systemui.R;
-
-public class UsbPreferenceActivity extends Activity implements View.OnClickListener {
-
- private static final String TAG = "UsbPreferenceActivity";
-
- private UsbManager mUsbManager;
- private String mCurrentFunction;
- private String[] mFunctions;
- private String mInstallerImagePath;
- private Button mMtpPtpButton;
- private Button mInstallerCdButton;
- private boolean mPtpActive;
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
-
- mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
-
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
- dialogBuilder.setTitle(getString(R.string.usb_preference_title));
-
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null);
- dialogBuilder.setView(buttonView);
- mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button);
- mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button);
- mMtpPtpButton.setOnClickListener(this);
- mInstallerCdButton.setOnClickListener(this);
-
- mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP);
- if (mPtpActive) {
- mMtpPtpButton.setText(R.string.use_mtp_button_title);
- }
-
- mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);
- if (!(new File(mInstallerImagePath)).exists()) {
- mInstallerCdButton.setVisibility(View.GONE);
- }
-
- dialogBuilder.show();
- }
-
- public void onClick(View v) {
- if (v.equals(mMtpPtpButton)) {
- if (mPtpActive) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_MTP);
- } else {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_PTP);
- mUsbManager.setDefaultFunction(UsbManager.USB_FUNCTION_PTP);
- }
- } else if (v.equals(mInstallerCdButton)) {
- mUsbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_MASS_STORAGE);
- mUsbManager.setMassStorageBackingFile(mInstallerImagePath);
- }
-
+/*
+ * Copyright (C) 2011 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.systemui.usb;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.util.Log;
+import android.widget.Button;
+
+import java.io.File;
+
+import com.android.systemui.R;
+
+public class UsbPreferenceActivity extends Activity implements View.OnClickListener {
+
+ private static final String TAG = "UsbPreferenceActivity";
+
+ private UsbManager mUsbManager;
+ private String mCurrentFunction;
+ private String[] mFunctions;
+ private String mInstallerImagePath;
+ private AlertDialog mDialog;
+ private Button mMtpPtpButton;
+ private Button mInstallerCdButton;
+ private boolean mPtpActive;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
+ dialogBuilder.setTitle(getString(R.string.usb_preference_title));
+
+ LayoutInflater inflater = (LayoutInflater)getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ View buttonView = inflater.inflate(R.layout.usb_preference_buttons, null);
+ dialogBuilder.setView(buttonView);
+ mMtpPtpButton = (Button)buttonView.findViewById(R.id.mtp_ptp_button);
+ mInstallerCdButton = (Button)buttonView.findViewById(R.id.installer_cd_button);
+ mMtpPtpButton.setOnClickListener(this);
+ mInstallerCdButton.setOnClickListener(this);
+
+ mPtpActive = mUsbManager.isFunctionEnabled(UsbManager.USB_FUNCTION_PTP);
+ if (mPtpActive) {
+ mMtpPtpButton.setText(R.string.use_mtp_button_title);
+ }
+
+ mInstallerImagePath = getString(com.android.internal.R.string.config_isoImagePath);
+ if (!(new File(mInstallerImagePath)).exists()) {
+ mInstallerCdButton.setVisibility(View.GONE);
+ }
+
+ mDialog = dialogBuilder.show();
+ }
+
+ public void onClick(View v) {
+ if (v.equals(mMtpPtpButton)) {
+ if (mPtpActive) {
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MTP, true);
+ } else {
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_PTP, true);
+ }
+ } else if (v.equals(mInstallerCdButton)) {
+ // installer CD is never default
+ mUsbManager.setCurrentFunction(UsbManager.USB_FUNCTION_MASS_STORAGE, false);
+ mUsbManager.setMassStorageBackingFile(mInstallerImagePath);
+ }
+
+ if (mDialog != null) {
+ mDialog.dismiss();
+ }
finish();
- }
-}
+ }
+}
diff --git a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
index c4feefd..de4ea75 100644
--- a/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/AccountUnlockScreen.java
@@ -110,7 +110,7 @@
mOk = (Button) findViewById(R.id.ok);
mOk.setOnClickListener(this);
- mEmergencyCall = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCall = (Button) findViewById(R.id.emergencyCallButton);
mEmergencyCall.setOnClickListener(this);
mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCall);
diff --git a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
index 6734005..f862d01 100644
--- a/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PasswordUnlockScreen.java
@@ -129,7 +129,7 @@
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
- mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
mEmergencyCallButton.setOnClickListener(this);
mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 75f466a..7f6327d 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -498,6 +498,7 @@
return;
}
+ int width = WRAP_CONTENT;
if (st.decorView == null || st.refreshDecorView) {
if (st.decorView == null) {
// Initialize the panel decor, this will populate st.decorView
@@ -523,6 +524,7 @@
// If the contents is fill parent for the width, set the
// corresponding background
backgroundResId = st.fullBackground;
+ width = MATCH_PARENT;
} else {
// Otherwise, set the normal panel background
backgroundResId = st.background;
@@ -546,7 +548,7 @@
st.isHandled = false;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- WRAP_CONTENT, WRAP_CONTENT,
+ width, WRAP_CONTENT,
st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
WindowManager.LayoutParams.FLAG_DITHER
| WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
@@ -709,9 +711,7 @@
mActionBar.isOverflowReserved()) {
if (mActionBar.getVisibility() == View.VISIBLE) {
if (!mActionBar.isOverflowMenuShowing()) {
- final Callback cb = getCallback();
- if (cb != null && !isDestroyed() &&
- cb.onPreparePanel(featureId, st.createdPanelView, st.menu)) {
+ if (!isDestroyed() && preparePanel(st, event)) {
playSoundEffect = mActionBar.showOverflowMenu();
}
} else {
diff --git a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
index 544bb3d..7e8d547 100644
--- a/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/SimPukUnlockScreen.java
@@ -109,7 +109,7 @@
mDelPinButton.setOnClickListener(this);
- mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
mOkButton = (TextView) findViewById(R.id.ok);
mHeaderText.setText(R.string.keyguard_password_enter_puk_code);
diff --git a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
index 7255c27..ec917f0 100644
--- a/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/SimUnlockScreen.java
@@ -99,7 +99,7 @@
mOkButton.setOnClickListener(this);
- mEmergencyCallButton = (Button) findViewById(R.id.emergencyCall);
+ mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
mEmergencyCallButton.setOnClickListener(this);
mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton);
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 44df5b5..f716e63 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -2936,7 +2936,7 @@
mStreamType = streamType;
// NOTE: audio_track_cblk_t::frameSize for 8 bit PCM data is based on a sample size of
// 16 bit because data is converted to 16 bit before being stored in buffer by AudioTrack
- mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * sizeof(int16_t) : sizeof(int8_t);
+ mCblk->frameSize = audio_is_linear_pcm(format) ? mChannelCount * audio_bytes_per_sample(format) : sizeof(uint8_t);
}
}
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index 8c07e15..5e54d61 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -255,10 +255,7 @@
// Update the kernel timezone information
// Kernel tracks time offsets as 'minutes west of GMT'
- int gmtOffset = zone.getRawOffset();
- if (zone.inDaylightTime(new Date(System.currentTimeMillis()))) {
- gmtOffset += zone.getDSTSavings();
- }
+ int gmtOffset = zone.getOffset(System.currentTimeMillis());
setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
}
@@ -759,9 +756,8 @@
// based off of the current Zone gmt offset + userspace tracked
// daylight savings information.
TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY));
- int gmtOffset = (zone.getRawOffset() + zone.getDSTSavings()) / 60000;
-
- setKernelTimezone(mDescriptor, -(gmtOffset));
+ int gmtOffset = zone.getOffset(System.currentTimeMillis());
+ setKernelTimezone(mDescriptor, -(gmtOffset / 60000));
scheduleDateChangedEvent();
}
}
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index aa3dfa6..e6f443a 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -466,12 +466,10 @@
INetworkManagementService nmService = INetworkManagementService.Stub.asInterface(b);
mTethering = new Tethering(mContext, nmService, mHandler.getLooper());
- mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) ||
- !mTethering.isDunRequired()) &&
- (mTethering.getTetherableUsbRegexs().length != 0 ||
+ mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
mTethering.getTetherableWifiRegexs().length != 0 ||
mTethering.getTetherableBluetoothRegexs().length != 0) &&
- mTethering.getUpstreamIfaceRegexs().length != 0);
+ mTethering.getUpstreamIfaceTypes().length != 0);
mVpn = new Vpn(mContext, new VpnCallback());
@@ -1576,12 +1574,6 @@
}
addPrivateDnsRoutes(mNetTrackers[netType]);
}
-
- /** Notify TetheringService if interface name has been changed. */
- if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
- Phone.REASON_LINK_PROPERTIES_CHANGED)) {
- handleTetherIfaceChange(netType);
- }
} else {
if (mNetConfigs[netType].isDefault()) {
removeDefaultRoute(mNetTrackers[netType]);
@@ -2412,14 +2404,6 @@
}
}
- private void handleTetherIfaceChange(int type) {
- String iface = mNetTrackers[type].getLinkProperties().getInterfaceName();
-
- if (isTetheringSupported()) {
- mTethering.handleTetherIfaceChange(iface);
- }
- }
-
private void log(String s) {
Slog.d(TAG, s);
}
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index fbde9d1..8037d7a 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -35,6 +35,8 @@
import android.app.ActivityManagerNative;
import android.app.AlertDialog;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -52,6 +54,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
+import android.graphics.BitmapFactory;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Environment;
@@ -154,6 +157,13 @@
private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
+ // Ongoing notification
+ private final NotificationManager mNotificationManager;
+ private final Notification mImeSwitcherNotification;
+ private final PendingIntent mImeSwitchPendingIntent;
+ private final boolean mShowOngoingImeSwitcherForPhones;
+ private boolean mNotificationShown;
+
class SessionState {
final ClientState client;
final IInputMethod method;
@@ -508,6 +518,25 @@
handleMessage(msg);
}
});
+
+ mNotificationManager = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ mImeSwitcherNotification = new Notification();
+ mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
+ mImeSwitcherNotification.when = 0;
+ mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
+ mImeSwitcherNotification.tickerText = null;
+ mImeSwitcherNotification.defaults = 0; // please be quiet
+ mImeSwitcherNotification.sound = null;
+ mImeSwitcherNotification.vibrate = null;
+ Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ mImeSwitchPendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
+ com.android.internal.R.bool.show_ongoing_ime_switcher);
+
synchronized (mMethodMap) {
mFileManager = new InputMethodFileManager(mMethodMap);
}
@@ -522,6 +551,7 @@
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
+ mNotificationShown = false;
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
@@ -1022,6 +1052,32 @@
}
}
+ private boolean needsToShowImeSwitchOngoingNotification() {
+ if (!mShowOngoingImeSwitcherForPhones) return false;
+ synchronized (mMethodMap) {
+ List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
+ final int N = imis.size();
+ int count = 0;
+ for(int i = 0; i < N; ++i) {
+ final InputMethodInfo imi = imis.get(i);
+ final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
+ imi, true);
+ final int subtypeCount = subtypes.size();
+ if (subtypeCount == 0) {
+ ++count;
+ } else {
+ for (int j = 0; j < subtypeCount; ++j) {
+ if (!subtypes.get(j).isAuxiliary()) {
+ ++count;
+ }
+ }
+ }
+ if (count > 1) return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
int uid = Binder.getCallingUid();
@@ -1036,6 +1092,25 @@
mImeWindowVis = vis;
mBackDisposition = backDisposition;
mStatusBar.setImeWindowStatus(token, vis, backDisposition);
+ final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
+ if (iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+ final PackageManager pm = mContext.getPackageManager();
+ final CharSequence label = mMethodMap.get(mCurMethodId).loadLabel(pm);
+ final CharSequence title = mRes.getText(
+ com.android.internal.R.string.select_input_method);
+ mImeSwitcherNotification.setLatestEventInfo(
+ mContext, title, label, mImeSwitchPendingIntent);
+ mNotificationManager.notify(
+ com.android.internal.R.string.select_input_method,
+ mImeSwitcherNotification);
+ mNotificationShown = true;
+ } else {
+ if (mNotificationShown) {
+ mNotificationManager.cancel(
+ com.android.internal.R.string.select_input_method);
+ mNotificationShown = false;
+ }
+ }
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index a8cb333..2b01c5e 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -19,6 +19,7 @@
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -1022,6 +1023,12 @@
return getInterfaceThrottle(iface, false);
}
+ @Override
+ public void setBandwidthControlEnabled(boolean enabled) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+ mConnector.doCommand(String.format("bandwidth %s", (enabled ? "enable" : "disable")));
+ }
+
/**
* Utility method to read a single plain-text {@link Long} from the given
* {@link File}, usually from a {@code /proc/} filesystem.
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index a9dfb22..f6dd43a 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -32,6 +32,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.ContentObserver;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
@@ -814,6 +815,8 @@
final AtomicInteger mInteractionIdCounter = new AtomicInteger();
+ final Rect mTempBounds = new Rect();
+
// the events pending events to be dispatched to this service
final SparseArray<AccessibilityEvent> mPendingEvents =
new SparseArray<AccessibilityEvent>();
@@ -932,9 +935,10 @@
AccessibilityNodeInfo info = mCallback.getFindAccessibilityNodeInfoResultAndClear(
interactionId);
if (info != null) {
+ applyCompatibilityScaleIfNeeded(info);
info.setConnection(this);
+ info.setSealed(true);
}
- info.setSealed(true);
return info;
} catch (RemoteException re) {
if (DEBUG) {
@@ -979,6 +983,7 @@
final int infoCount = infos.size();
for (int i = 0; i < infoCount; i++) {
AccessibilityNodeInfo info = infos.get(i);
+ applyCompatibilityScaleIfNeeded(info);
info.setConnection(this);
info.setSealed(true);
}
@@ -1019,6 +1024,7 @@
AccessibilityNodeInfo info =
mCallback.getFindAccessibilityNodeInfoResultAndClear(interactionId);
if (info != null) {
+ applyCompatibilityScaleIfNeeded(info);
info.setConnection(this);
info.setSealed(true);
}
@@ -1093,6 +1099,24 @@
}
return mWindowIdToInteractionConnectionMap.get(windowId);
}
+
+ private void applyCompatibilityScaleIfNeeded(AccessibilityNodeInfo info) {
+ IBinder windowToken = mWindowIdToWindowTokenMap.get(info.getWindowId());
+ final float scale = mWindowManagerService.getWindowCompatibilityScale(windowToken);
+
+ if (scale == 1.0f) {
+ return;
+ }
+
+ Rect bounds = mTempBounds;
+ info.getBoundsInParent(bounds);
+ bounds.scale(scale);
+ info.setBoundsInParent(bounds);
+
+ info.getBoundsInScreen(bounds);
+ bounds.scale(scale);
+ info.setBoundsInScreen(bounds);
+ }
}
final class SecurityPolicy {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index d8772b8..b94ee58 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -73,7 +73,7 @@
*/
final class ActivityStack {
static final String TAG = ActivityManagerService.TAG;
- static final boolean localLOGV = ActivityManagerService.localLOGV || true;
+ static final boolean localLOGV = ActivityManagerService.localLOGV;
static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH;
static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE;
static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY;
diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java
index a769c05..917259a 100644
--- a/services/java/com/android/server/am/AppErrorDialog.java
+++ b/services/java/com/android/server/am/AppErrorDialog.java
@@ -60,12 +60,12 @@
setCancelable(false);
- setButton(DialogInterface.BUTTON_POSITIVE,
+ setButton(DialogInterface.BUTTON_NEGATIVE,
res.getText(com.android.internal.R.string.force_close),
mHandler.obtainMessage(FORCE_QUIT));
if (app.errorReportReceiver != null) {
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_POSITIVE,
res.getText(com.android.internal.R.string.report),
mHandler.obtainMessage(FORCE_QUIT_AND_REPORT));
}
diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java
index b2737dc..b40f87a9 100644
--- a/services/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/java/com/android/server/am/AppNotRespondingDialog.java
@@ -77,7 +77,7 @@
? res.getString(resid, name1.toString(), name2.toString())
: res.getString(resid, name1.toString()));
- setButton(DialogInterface.BUTTON_POSITIVE,
+ setButton(DialogInterface.BUTTON_NEGATIVE,
res.getText(com.android.internal.R.string.force_close),
mHandler.obtainMessage(FORCE_CLOSE));
setButton(DialogInterface.BUTTON_NEUTRAL,
@@ -85,7 +85,7 @@
mHandler.obtainMessage(WAIT));
if (app.errorReportReceiver != null) {
- setButton(DialogInterface.BUTTON_NEGATIVE,
+ setButton(DialogInterface.BUTTON_POSITIVE,
res.getText(com.android.internal.R.string.report),
mHandler.obtainMessage(WAIT_AND_REPORT));
}
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 15e67d0..911cac2 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -57,7 +57,9 @@
import java.io.PrintWriter;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
/**
@@ -82,7 +84,15 @@
private String[] mTetherableUsbRegexs;
private String[] mTetherableWifiRegexs;
private String[] mTetherableBluetoothRegexs;
- private String[] mUpstreamIfaceRegexs;
+ private Collection<Integer> mUpstreamIfaceTypes;
+
+ private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
+ private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
+ private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
+
+ // if we have to connect to mobile, what APN type should we use? Calculated by examining the
+ // upstream type list and the DUN_REQUIRED secure-setting
+ private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE;
private INetworkManagementService mNMService;
private Looper mLooper;
@@ -112,9 +122,6 @@
private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
- // resampled each time we turn on tethering - used as cache for settings/config-val
- private boolean mDunRequired; // configuration info - must use DUN apn on 3g
-
private StateMachine mTetherMasterSM;
private Notification mTetheredNotification;
@@ -159,7 +166,6 @@
if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
mDhcpRange = DHCP_DEFAULT_RANGE;
}
- mDunRequired = false; // resample when we turn on
mTetherableUsbRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_usb_regexs);
@@ -167,8 +173,15 @@
com.android.internal.R.array.config_tether_wifi_regexs);
mTetherableBluetoothRegexs = context.getResources().getStringArray(
com.android.internal.R.array.config_tether_bluetooth_regexs);
- mUpstreamIfaceRegexs = context.getResources().getStringArray(
- com.android.internal.R.array.config_tether_upstream_regexs);
+ int ifaceTypes[] = context.getResources().getIntArray(
+ com.android.internal.R.array.config_tether_upstream_types);
+ mUpstreamIfaceTypes = new ArrayList();
+ for (int i : ifaceTypes) {
+ mUpstreamIfaceTypes.add(new Integer(i));
+ }
+
+ // check if the upstream type list needs to be modified due to secure-settings
+ checkDunRequired();
// TODO - remove and rely on real notifications of the current iface
mDnsServers = new String[2];
@@ -509,9 +522,9 @@
}
try {
if (enabled) {
- usbManager.setPrimaryFunction(UsbManager.USB_FUNCTION_RNDIS);
+ usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
} else {
- usbManager.setPrimaryFunction(null);
+ usbManager.setCurrentFunction(null, false);
}
} catch (Exception e) {
Log.e(TAG, "Error toggling usb RNDIS", e);
@@ -582,16 +595,44 @@
return mTetherableBluetoothRegexs;
}
- public String[] getUpstreamIfaceRegexs() {
- return mUpstreamIfaceRegexs;
+ public int[] getUpstreamIfaceTypes() {
+ int values[] = new int[mUpstreamIfaceTypes.size()];
+ Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator();
+ for (int i=0; i < mUpstreamIfaceTypes.size(); i++) {
+ values[i] = iterator.next();
+ }
+ return values;
}
- public boolean isDunRequired() {
- boolean defaultVal = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_tether_dun_required);
- boolean result = (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.TETHER_DUN_REQUIRED, (defaultVal ? 1 : 0)) == 1);
- return result;
+ public void checkDunRequired() {
+ int requiredApn = ((Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.TETHER_DUN_REQUIRED, 0) == 1) ?
+ ConnectivityManager.TYPE_MOBILE_DUN :
+ ConnectivityManager.TYPE_MOBILE_HIPRI);
+ if (mPreferredUpstreamMobileApn != requiredApn) {
+ if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) {
+ while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) {
+ mUpstreamIfaceTypes.remove(MOBILE_TYPE);
+ }
+ while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) {
+ mUpstreamIfaceTypes.remove(HIPRI_TYPE);
+ }
+ if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) {
+ mUpstreamIfaceTypes.add(DUN_TYPE);
+ }
+ } else {
+ while (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
+ mUpstreamIfaceTypes.remove(DUN_TYPE);
+ }
+ if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) {
+ mUpstreamIfaceTypes.add(MOBILE_TYPE);
+ }
+ if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) {
+ mUpstreamIfaceTypes.add(HIPRI_TYPE);
+ }
+ }
+ mPreferredUpstreamMobileApn = requiredApn;
+ }
}
public String[] getTetheredIfaces() {
@@ -648,17 +689,6 @@
return retVal;
}
- public void handleTetherIfaceChange(String iface) {
- // check if iface is white listed
- for (String regex : mUpstreamIfaceRegexs) {
- if (iface.matches(regex)) {
- if (DEBUG) Log.d(TAG, "Tethering got Interface Change");
- mTetherMasterSM.sendMessage(TetherMasterSM.CMD_IFACE_CHANGED, iface);
- break;
- }
- }
- }
-
class TetherInterfaceSM extends StateMachine {
// notification from the master SM that it's not in tether mode
static final int CMD_TETHER_MODE_DEAD = 1;
@@ -1051,8 +1081,6 @@
static final int CMD_CELL_CONNECTION_RENEW = 4;
// we don't have a valid upstream conn, check again after a delay
static final int CMD_RETRY_UPSTREAM = 5;
- // received an indication that upstream interface has changed
- static final int CMD_IFACE_CHANGED = 6;
// This indicates what a timeout event relates to. A state that
// sends itself a delayed timeout event and handles incoming timeout events
@@ -1072,7 +1100,7 @@
private ArrayList mNotifyList;
private int mCurrentConnectionSequence;
- private boolean mMobileReserved = false;
+ private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
private String mUpstreamIfaceName = null;
@@ -1111,22 +1139,34 @@
public boolean processMessage(Message m) {
return false;
}
- protected boolean turnOnMobileConnection() {
+ protected String enableString(int apnType) {
+ switch (apnType) {
+ case ConnectivityManager.TYPE_MOBILE_DUN:
+ return Phone.FEATURE_ENABLE_DUN_ALWAYS;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ return Phone.FEATURE_ENABLE_HIPRI;
+ }
+ return null;
+ }
+ protected boolean turnOnUpstreamMobileConnection(int apnType) {
boolean retValue = true;
- if (mMobileReserved) return retValue;
+ if (apnType == ConnectivityManager.TYPE_NONE) return false;
+ if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection();
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
int result = Phone.APN_REQUEST_FAILED;
+ String enableString = enableString(apnType);
+ if (enableString == null) return false;
try {
result = cm.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- (mDunRequired ? Phone.FEATURE_ENABLE_DUN_ALWAYS :
- Phone.FEATURE_ENABLE_HIPRI), new Binder());
+ enableString, new Binder());
} catch (Exception e) {
}
switch (result) {
case Phone.APN_ALREADY_ACTIVE:
case Phone.APN_REQUEST_STARTED:
- mMobileReserved = true;
+ mMobileApnReserved = apnType;
Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW);
m.arg1 = ++mCurrentConnectionSequence;
sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS);
@@ -1139,18 +1179,17 @@
return retValue;
}
- protected boolean turnOffMobileConnection() {
- if (mMobileReserved) {
+ protected boolean turnOffUpstreamMobileConnection() {
+ if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
try {
cm.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
- (mDunRequired? Phone.FEATURE_ENABLE_DUN_ALWAYS :
- Phone.FEATURE_ENABLE_HIPRI));
+ enableString(mMobileApnReserved));
} catch (Exception e) {
return false;
}
- mMobileReserved = false;
+ mMobileApnReserved = ConnectivityManager.TYPE_NONE;
}
return true;
}
@@ -1196,108 +1235,55 @@
transitionTo(mInitialState);
return true;
}
- protected String findActiveUpstreamIface() {
- // check for what iface we can use - if none found switch to error.
- IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
-
- try {
- LinkProperties defaultProp = cm.getActiveLinkProperties();
- if (defaultProp != null) {
- String iface = defaultProp.getInterfaceName();
- for(String regex : mUpstreamIfaceRegexs) {
- if (iface.matches(regex)) return iface;
- }
- }
- } catch (RemoteException e) { }
-
- String[] ifaces = new String[0];
- try {
- ifaces = mNMService.listInterfaces();
- } catch (Exception e) {
- Log.e(TAG, "Error listing Interfaces", e);
- return null;
- }
-
- for (String regex : mUpstreamIfaceRegexs) {
- for (String iface : ifaces) {
- if (iface.matches(regex)) {
- // verify it is active
- InterfaceConfiguration ifcg = null;
- try {
- ifcg = mNMService.getInterfaceConfig(iface);
- if (ifcg.isActive()) {
- return iface;
- }
- } catch (Exception e) {
- Log.e(TAG, "Error getting iface config", e);
- // ignore - try next
- continue;
- }
- }
- }
- }
- return null;
- }
protected void chooseUpstreamType(boolean tryCell) {
- // decide if the current upstream is good or not and if not
- // do something about it (start up DUN if required or HiPri if not)
- String iface = findActiveUpstreamIface();
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
IConnectivityManager cm = IConnectivityManager.Stub.asInterface(b);
- mMobileReserved = false;
- if (DEBUG) {
- Log.d(TAG, "chooseUpstreamType(" + tryCell + "), dunRequired ="
- + mDunRequired + ", iface=" + iface);
- }
- if (iface != null) {
+ int upType = ConnectivityManager.TYPE_NONE;
+ String iface = null;
+
+ for (Integer netType : mUpstreamIfaceTypes) {
+ NetworkInfo info = null;
try {
- if (mDunRequired) {
- // check if Dun is on - we can use that
- NetworkInfo info = cm.getNetworkInfo(
- ConnectivityManager.TYPE_MOBILE_DUN);
- if (info.isConnected()) {
- if (DEBUG) Log.d(TAG, "setting dun ifacename =" + iface);
- // even if we're already connected - it may be somebody else's
- // refcount, so add our own
- turnOnMobileConnection();
- } else {
- // verify the iface is not the default mobile - can't use that!
- info = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
- if (info.isConnected()) {
- iface = null; // can't accept this one
- }
- }
- } else {
- if (DEBUG) Log.d(TAG, "checking if hipri brought us this connection");
- NetworkInfo info = cm.getNetworkInfo(
- ConnectivityManager.TYPE_MOBILE_HIPRI);
- if (info.isConnected()) {
- if (DEBUG) Log.d(TAG, "yes - hipri in use");
- // even if we're already connected - it may be sombody else's
- // refcount, so add our own
- turnOnMobileConnection();
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException calling ConnectivityManager", e);
- iface = null;
+ info = cm.getNetworkInfo(netType.intValue());
+ } catch (RemoteException e) { }
+ if ((info != null) && info.isConnected()) {
+ upType = netType.intValue();
+ break;
}
}
- // may have been set to null in the if above
- if (iface == null ) {
- boolean success = false;
- if (tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) {
- success = turnOnMobileConnection();
+
+ if (DEBUG) {
+ Log.d(TAG, "chooseUpstreamType(" + tryCell + "), preferredApn ="
+ + mPreferredUpstreamMobileApn + ", got type=" + upType);
+ }
+
+ // if we're on DUN, put our own grab on it
+ if (upType == ConnectivityManager.TYPE_MOBILE_DUN ||
+ upType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
+ turnOnUpstreamMobileConnection(upType);
+ }
+
+ if (upType == ConnectivityManager.TYPE_NONE) {
+ boolean tryAgainLater = true;
+ if ((tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) &&
+ (turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn) == true)) {
+ // we think mobile should be coming up - don't set a retry
+ tryAgainLater = false;
}
- if (!success) {
- // wait for things to settle and retry
+ if (tryAgainLater) {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
}
+ } else {
+ LinkProperties linkProperties = null;
+ try {
+ linkProperties = cm.getLinkProperties(upType);
+ } catch (RemoteException e) { }
+ if (linkProperties != null) iface = linkProperties.getInterfaceName();
}
notifyTetheredOfNewUpstreamIface(iface);
}
+
protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
if (DEBUG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
mUpstreamIfaceName = ifaceName;
@@ -1312,7 +1298,6 @@
class InitialState extends TetherMasterUtilState {
@Override
public void enter() {
- mMobileReserved = false;
}
@Override
public boolean processMessage(Message message) {
@@ -1320,7 +1305,7 @@
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_MODE_REQUESTED:
- mDunRequired = isDunRequired();
+ checkDunRequired();
TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
if (DEBUG) Log.d(TAG, "Tether Mode requested by " + who.toString());
mNotifyList.add(who);
@@ -1354,7 +1339,7 @@
}
@Override
public void exit() {
- turnOffMobileConnection();
+ turnOffUpstreamMobileConnection();
notifyTetheredOfNewUpstreamIface(null);
}
@Override
@@ -1392,19 +1377,13 @@
Log.d(TAG, "renewing mobile connection - requeuing for another " +
CELL_CONNECTION_RENEW_MS + "ms");
}
- mMobileReserved = false; // need to renew it
- turnOnMobileConnection();
+ turnOnUpstreamMobileConnection(mMobileApnReserved);
}
break;
case CMD_RETRY_UPSTREAM:
chooseUpstreamType(mTryCell);
mTryCell = !mTryCell;
break;
- case CMD_IFACE_CHANGED:
- String iface = (String)message.obj;
- if (DEBUG) Log.d(TAG, "Activie upstream interface changed: " + iface);
- notifyTetheredOfNewUpstreamIface(iface);
- break;
default:
retValue = false;
break;
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index 043a581..524dd40 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -27,6 +27,7 @@
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
import static android.net.TrafficStats.UID_REMOVED;
+import static android.provider.Settings.Secure.NETSTATS_ENABLED;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_BUCKET_DURATION;
import static android.provider.Settings.Secure.NETSTATS_NETWORK_MAX_HISTORY;
import static android.provider.Settings.Secure.NETSTATS_PERSIST_THRESHOLD;
@@ -70,6 +71,7 @@
import android.util.TrustedTime;
import com.android.internal.os.AtomicFile;
+import com.android.server.NativeDaemonConnectorException;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
@@ -134,6 +136,7 @@
* Settings that can be changed externally.
*/
public interface NetworkStatsSettings {
+ public boolean getEnabled();
public long getPollInterval();
public long getPersistThreshold();
public long getNetworkBucketDuration();
@@ -206,6 +209,20 @@
}
public void systemReady() {
+ if (mSettings.getEnabled()) {
+ try {
+ // enable low-level bandwidth stats and control
+ // TODO: consider shipping with this enabled by default
+ mNetworkManager.setBandwidthControlEnabled(true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "problem talking to netd while enabling bandwidth controls", e);
+ } catch (NativeDaemonConnectorException ndce) {
+ Slog.e(TAG, "problem enabling bandwidth controls", ndce);
+ }
+ } else {
+ Slog.w(TAG, "detailed network stats disabled");
+ }
+
synchronized (mStatsLock) {
// read historical network stats from disk, since policy service
// might need them right away. we delay loading detailed UID stats
@@ -1040,6 +1057,13 @@
return Settings.Secure.getLong(mResolver, name, def);
}
+ public boolean getEnabled() {
+ if (!new File("/proc/net/xt_qtaguid/ctrl").exists()) {
+ Slog.w(TAG, "kernel does not support bandwidth control");
+ return false;
+ }
+ return Settings.Secure.getInt(mResolver, NETSTATS_ENABLED, 1) != 0;
+ }
public long getPollInterval() {
return getSecureLong(NETSTATS_POLL_INTERVAL, 15 * MINUTE_IN_MILLIS);
}
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index b7f9d5c..918f1b6 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -77,9 +77,8 @@
private static final int MSG_UPDATE_STATE = 0;
private static final int MSG_ENABLE_ADB = 1;
- private static final int MSG_SET_PRIMARY_FUNCTION = 2;
- private static final int MSG_SET_DEFAULT_FUNCTION = 3;
- private static final int MSG_SYSTEM_READY = 4;
+ private static final int MSG_SET_CURRENT_FUNCTION = 2;
+ private static final int MSG_SYSTEM_READY = 3;
// Delay for debouncing USB disconnects.
// We often get rapid connect/disconnect events when enabling USB functions,
@@ -227,7 +226,7 @@
mHandler.updateState(state);
} else if ("START".equals(accessory)) {
Slog.d(TAG, "got accessory start");
- setPrimaryFunction(UsbManager.USB_FUNCTION_ACCESSORY);
+ setCurrentFunction(UsbManager.USB_FUNCTION_ACCESSORY, false);
}
}
};
@@ -371,6 +370,14 @@
sendMessage(m);
}
+ public void sendMessage(int what, Object arg0, boolean arg1) {
+ removeMessages(what);
+ Message m = Message.obtain(this, what);
+ m.obj = arg0;
+ m.arg1 = (arg1 ? 1 : 0);
+ sendMessage(m);
+ }
+
public void updateState(String state) {
int connected, configured;
@@ -395,24 +402,30 @@
sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
}
- private boolean setUsbConfig(String config) {
- // set the new configuration
- SystemProperties.set("sys.usb.config", config);
+ private boolean waitForState(String state) {
// wait for the transition to complete.
// give up after 1 second.
for (int i = 0; i < 20; i++) {
// State transition is done when sys.usb.conf.done is set to the new configuration
- if (config.equals(SystemProperties.get("sys.usb.state"))) return true;
+ if (state.equals(SystemProperties.get("sys.usb.state"))) return true;
try {
// try again in 50ms
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
+ Log.e(TAG, "waitForState(" + state + ") FAILED");
return false;
}
- private void setCurrentFunctions(String functions) {
+ private boolean setUsbConfig(String config) {
+ Log.d(TAG, "setUsbConfig(" + config + ")");
+ // set the new configuration
+ SystemProperties.set("sys.usb.config", config);
+ return waitForState(config);
+ }
+
+ private void doSetCurrentFunctions(String functions) {
if (!mCurrentFunctions.equals(functions)) {
if (!setUsbConfig("none") || !setUsbConfig(functions)) {
Log.e(TAG, "Failed to switch USB configuration to " + functions);
@@ -428,17 +441,14 @@
if (enable != mAdbEnabled) {
mAdbEnabled = enable;
String functions;
+ // Due to the persist.sys.usb.config property trigger, changing adb state requires
+ // switching to default function
if (enable) {
- functions = addFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
- mDefaultFunctions = addFunction(mDefaultFunctions,
- UsbManager.USB_FUNCTION_ADB);
+ functions = addFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB);
} else {
- functions = removeFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
- mDefaultFunctions = removeFunction(mDefaultFunctions,
- UsbManager.USB_FUNCTION_ADB);
+ functions = removeFunction(mDefaultFunctions, UsbManager.USB_FUNCTION_ADB);
}
- SystemProperties.set("persist.sys.usb.config", mDefaultFunctions);
- setCurrentFunctions(functions);
+ setCurrentFunction(functions, true);
updateAdbNotification(mAdbEnabled && mConnected);
}
}
@@ -449,7 +459,7 @@
} else {
functionList = removeFunction(functionList, UsbManager.USB_FUNCTION_ADB);
}
- setCurrentFunctions(functionList);
+ doSetCurrentFunctions(functionList);
}
private void updateCurrentAccessory() {
@@ -503,8 +513,6 @@
@Override
public void handleMessage(Message msg) {
- String function;
-
switch (msg.what) {
case MSG_UPDATE_STATE:
mConnected = (msg.arg1 == 1);
@@ -518,7 +526,7 @@
if (!mConnected) {
// restore defaults when USB is disconnected
- setCurrentFunctions(mDefaultFunctions);
+ doSetCurrentFunctions(mDefaultFunctions);
}
if (mSystemReady) {
updateUsbState();
@@ -527,20 +535,31 @@
case MSG_ENABLE_ADB:
setAdbEnabled(msg.arg1 == 1);
break;
- case MSG_SET_PRIMARY_FUNCTION:
- function = (String)msg.obj;
- if (function == null) {
- function = mDefaultFunctions;
+ case MSG_SET_CURRENT_FUNCTION:
+ String function = (String)msg.obj;
+ boolean makeDefault = (msg.arg1 == 1);
+ if (makeDefault) {
+ if (function == null) {
+ throw new NullPointerException();
+ }
+ if (mAdbEnabled) {
+ function = addFunction(function, UsbManager.USB_FUNCTION_ADB);
+ }
+
+ setUsbConfig("none");
+ // setting this property will change the current USB state
+ // via a property trigger
+ SystemProperties.set("persist.sys.usb.config", function);
+ if (waitForState(function)) {
+ mCurrentFunctions = function;
+ mDefaultFunctions = function;
+ }
+ } else {
+ if (function == null) {
+ function = mDefaultFunctions;
+ }
+ setEnabledFunctions(function);
}
- setEnabledFunctions(function);
- break;
- case MSG_SET_DEFAULT_FUNCTION:
- function = (String)msg.obj;
- if (mAdbEnabled) {
- function = addFunction(function, UsbManager.USB_FUNCTION_ADB);
- }
- SystemProperties.set("persist.sys.usb.config", function);
- mDefaultFunctions = function;
break;
case MSG_SYSTEM_READY:
updateUsbNotification(mConnected);
@@ -588,15 +607,8 @@
return nativeOpenAccessory();
}
- public void setPrimaryFunction(String function) {
- mHandler.sendMessage(MSG_SET_PRIMARY_FUNCTION, function);
- }
-
- public void setDefaultFunction(String function) {
- if (function == null) {
- throw new NullPointerException();
- }
- mHandler.sendMessage(MSG_SET_DEFAULT_FUNCTION, function);
+ public void setCurrentFunction(String function, boolean makeDefault) {
+ mHandler.sendMessage(MSG_SET_CURRENT_FUNCTION, function, makeDefault);
}
public void setMassStorageBackingFile(String path) {
diff --git a/services/java/com/android/server/usb/UsbService.java b/services/java/com/android/server/usb/UsbService.java
index 193638f..9f2c17a 100644
--- a/services/java/com/android/server/usb/UsbService.java
+++ b/services/java/com/android/server/usb/UsbService.java
@@ -146,19 +146,10 @@
mSettingsManager.clearDefaults(packageName);
}
- public void setPrimaryFunction(String function) {
+ public void setCurrentFunction(String function, boolean makeDefault) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
if (mDeviceManager != null) {
- mDeviceManager.setPrimaryFunction(function);
- } else {
- throw new IllegalStateException("USB device mode not supported");
- }
- }
-
- public void setDefaultFunction(String function) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
- if (mDeviceManager != null) {
- mDeviceManager.setDefaultFunction(function);
+ mDeviceManager.setCurrentFunction(function, makeDefault);
} else {
throw new IllegalStateException("USB device mode not supported");
}
diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java
index 3be8af6..6806634 100644
--- a/services/java/com/android/server/wm/InputMonitor.java
+++ b/services/java/com/android/server/wm/InputMonitor.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.graphics.Rect;
+import android.os.Binder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
@@ -152,6 +153,8 @@
}
mUpdateInputWindowsNeeded = false;
+ if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED updateInputWindowsLw");
+
// Populate the input window list with information about all of the windows that
// could potentially receive input.
// As an optimization, we could try to prune the list of windows but this turns
@@ -232,6 +235,8 @@
// Clear the list in preparation for the next round.
// Also avoids keeping InputChannel objects referenced unnecessarily.
mTempInputWindows.clear();
+
+ if (false) Slog.d(WindowManagerService.TAG, "<<<<<<< EXITED updateInputWindowsLw");
}
/* Notifies that the input device configuration has changed. */
diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java
index 0f09356..50b251f 100644
--- a/services/java/com/android/server/wm/Session.java
+++ b/services/java/com/android/server/wm/Session.java
@@ -153,11 +153,13 @@
int requestedWidth, int requestedHeight, int viewFlags,
boolean insetsPending, Rect outFrame, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
- //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid());
+ if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from "
+ + Binder.getCallingPid());
int res = mService.relayoutWindow(this, window, attrs,
requestedWidth, requestedHeight, viewFlags, insetsPending,
outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface);
- //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid());
+ if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to "
+ + Binder.getCallingPid());
return res;
}
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 1c87f5b..3bf309b 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -2776,6 +2776,13 @@
Binder.restoreCallingIdentity(origId);
}
+ public float getWindowCompatibilityScale(IBinder windowToken) {
+ synchronized (mWindowMap) {
+ WindowState windowState = mWindowMap.get(windowToken);
+ return (windowState != null) ? windowState.mGlobalScale : 1.0f;
+ }
+ }
+
private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
+ (lp != null ? lp.packageName : null)
@@ -5557,10 +5564,10 @@
private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm,
int dw, int dh) {
- dm.unscaledWidthPixels = mPolicy.getNonDecorDisplayWidth(rotation, dw);
- dm.unscaledHeightPixels = mPolicy.getNonDecorDisplayHeight(rotation, dh);
+ dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(rotation, dw);
+ dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(rotation, dh);
float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
- int size = (int)(((dm.unscaledWidthPixels / scale) / dm.density) + .5f);
+ int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
if (curSize == 0 || size < curSize) {
curSize = size;
}
@@ -5635,9 +5642,9 @@
// Override display width and height with what we are computing,
// to be sure they remain consistent.
- dm.widthPixels = dm.unscaledWidthPixels = mAppDisplayWidth
+ dm.widthPixels = dm.noncompatWidthPixels = mAppDisplayWidth
= mPolicy.getNonDecorDisplayWidth(mRotation, dw);
- dm.heightPixels = dm.unscaledHeightPixels = mAppDisplayHeight
+ dm.heightPixels = dm.noncompatHeightPixels = mAppDisplayHeight
= mPolicy.getNonDecorDisplayHeight(mRotation, dh);
mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm,
@@ -7817,18 +7824,30 @@
TAG, "Placing surface #" + i + " " + w.mSurface
+ ": new=" + w.mShownFrame);
- int width, height;
- if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
- // for a scaled surface, we just want to use
- // the requested size.
- width = w.mRequestedWidth;
- height = w.mRequestedHeight;
- } else {
- width = w.mCompatFrame.width();
- height = w.mCompatFrame.height();
- }
-
if (w.mSurface != null) {
+ int width, height;
+ if ((w.mAttrs.flags & w.mAttrs.FLAG_SCALED) != 0) {
+ // for a scaled surface, we just want to use
+ // the requested size.
+ width = w.mRequestedWidth;
+ height = w.mRequestedHeight;
+ } else {
+ width = w.mCompatFrame.width();
+ height = w.mCompatFrame.height();
+ }
+
+ if (width < 1) {
+ width = 1;
+ }
+ if (height < 1) {
+ height = 1;
+ }
+ final boolean surfaceResized = w.mSurfaceW != width || w.mSurfaceH != height;
+ if (surfaceResized) {
+ w.mSurfaceW = width;
+ w.mSurfaceH = height;
+ }
+
if (w.mSurfaceX != w.mShownFrame.left
|| w.mSurfaceY != w.mShownFrame.top) {
try {
@@ -7848,21 +7867,11 @@
}
}
- if (width < 1) {
- width = 1;
- }
- if (height < 1) {
- height = 1;
- }
-
- if (w.mSurfaceW != width || w.mSurfaceH != height) {
+ if (surfaceResized) {
try {
if (SHOW_TRANSACTIONS) logSurface(w,
- "SIZE " + w.mShownFrame.width() + "x"
- + w.mShownFrame.height(), null);
+ "SIZE " + width + "x" + height, null);
w.mSurfaceResized = true;
- w.mSurfaceW = width;
- w.mSurfaceH = height;
w.mSurface.setSize(width, height);
} catch (RuntimeException e) {
// If something goes wrong with the surface (such
@@ -7878,9 +7887,9 @@
}
if (!w.mAppFreezing && w.mLayoutSeq == mLayoutSeq) {
- w.mContentInsetsChanged =
+ w.mContentInsetsChanged |=
!w.mLastContentInsets.equals(w.mContentInsets);
- w.mVisibleInsetsChanged =
+ w.mVisibleInsetsChanged |=
!w.mLastVisibleInsets.equals(w.mVisibleInsets);
boolean configChanged =
w.mConfiguration != mCurConfiguration
@@ -7892,24 +7901,20 @@
}
if (localLOGV) Slog.v(TAG, "Resizing " + w
+ ": configChanged=" + configChanged
- + " last=" + w.mLastCompatFrame + " frame=" + w.mCompatFrame);
- boolean frameChanged = !w.mLastCompatFrame.equals(w.mCompatFrame);
- if (frameChanged
- || w.mContentInsetsChanged
+ + " last=" + w.mLastFrame + " frame=" + w.mFrame);
+ w.mLastFrame.set(w.mFrame);
+ if (w.mContentInsetsChanged
|| w.mVisibleInsetsChanged
|| w.mSurfaceResized
|| configChanged) {
if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
Slog.v(TAG, "Resize reasons: "
- + "frameChanged=" + frameChanged
+ " contentInsetsChanged=" + w.mContentInsetsChanged
+ " visibleInsetsChanged=" + w.mVisibleInsetsChanged
+ " surfaceResized=" + w.mSurfaceResized
+ " configChanged=" + configChanged);
}
- w.mLastFrame.set(w.mFrame);
- w.mLastCompatFrame.set(w.mCompatFrame);
w.mLastContentInsets.set(w.mContentInsets);
w.mLastVisibleInsets.set(w.mVisibleInsets);
// If the screen is currently frozen, then keep
@@ -7944,9 +7949,12 @@
w.mAppToken.allDrawn = false;
}
}
- if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG,
- "Resizing window " + w + " to " + w.mCompatFrame);
- mResizingWindows.add(w);
+ if (!mResizingWindows.contains(w)) {
+ if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG,
+ "Resizing window " + w + " to " + w.mSurfaceW
+ + "x" + w.mSurfaceH);
+ mResizingWindows.add(w);
+ }
} else if (w.mOrientationChanging) {
if (!w.mDrawPending && !w.mCommitDrawPending) {
if (DEBUG_ORIENTATION) Slog.v(TAG,
@@ -8241,13 +8249,12 @@
if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION)
&& configChanged) {
Slog.i(TAG, "Sending new config to window " + win + ": "
- + win.mCompatFrame.width() + "x" + win.mCompatFrame.height()
+ + win.mSurfaceW + "x" + win.mSurfaceH
+ " / " + mCurConfiguration + " / 0x"
+ Integer.toHexString(diff));
}
win.mConfiguration = mCurConfiguration;
- win.mClient.resized(win.mCompatFrame.width(),
- win.mCompatFrame.height(), win.mLastContentInsets,
+ win.mClient.resized(win.mSurfaceW, win.mSurfaceH, win.mLastContentInsets,
win.mLastVisibleInsets, win.mDrawPending,
configChanged ? win.mConfiguration : null);
win.mContentInsetsChanged = false;
diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java
index 587685e..b370ec9 100644
--- a/services/java/com/android/server/wm/WindowState.java
+++ b/services/java/com/android/server/wm/WindowState.java
@@ -111,7 +111,6 @@
* applied).
*/
final Rect mShownFrame = new Rect();
- final Rect mLastShownFrame = new Rect();
/**
* Set when we have changed the size of the surface, to know that
@@ -182,7 +181,6 @@
// Frame that is scaled to the application's coordinate space when in
// screen size compatibility mode.
final Rect mCompatFrame = new Rect();
- final Rect mLastCompatFrame = new Rect();
final Rect mContainingFrame = new Rect();
final Rect mDisplayFrame = new Rect();
@@ -1584,15 +1582,12 @@
}
pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration);
pw.print(prefix); pw.print("mShownFrame=");
- mShownFrame.printShortString(pw);
- pw.print(" last="); mLastShownFrame.printShortString(pw);
- pw.println();
+ mShownFrame.printShortString(pw); pw.println();
pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
pw.print(" last="); mLastFrame.printShortString(pw);
pw.println();
if (mEnforceSizeCompat) {
pw.print(prefix); pw.print("mCompatFrame="); mCompatFrame.printShortString(pw);
- pw.print(" last="); mLastCompatFrame.printShortString(pw);
pw.println();
}
pw.print(prefix); pw.print("mContainingFrame=");
diff --git a/telephony/java/com/android/internal/telephony/ApnContext.java b/telephony/java/com/android/internal/telephony/ApnContext.java
index 496c43c..5ec00e8 100644
--- a/telephony/java/com/android/internal/telephony/ApnContext.java
+++ b/telephony/java/com/android/internal/telephony/ApnContext.java
@@ -89,6 +89,11 @@
}
public synchronized void setDataConnectionAc(DataConnectionAc dcac) {
+ if (dcac != null) {
+ dcac.addApnContext(this);
+ } else {
+ if (mDataConnectionAc != null) mDataConnectionAc.removeApnContext(this);
+ }
mDataConnectionAc = dcac;
}
diff --git a/telephony/java/com/android/internal/telephony/CallerInfo.java b/telephony/java/com/android/internal/telephony/CallerInfo.java
index 457fa7aa..ab93e2a 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfo.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfo.java
@@ -30,8 +30,8 @@
import android.text.TextUtils;
import android.util.Log;
+import com.google.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
import com.google.i18n.phonenumbers.NumberParseException;
-import com.google.i18n.phonenumbers.PhoneNumberOfflineGeocoder;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
@@ -526,8 +526,20 @@
+ countryIso);
}
+ // Temp workaround: The current libphonenumber library requires
+ // the countryIso to be uppercase (e.g. "US") but the
+ // detector.detectCountry().getCountryIso() call currently returns
+ // "us". Passing "us" to util.parse() will just result in a
+ // NumberParseException.
+ // So force the countryIso to uppercase for now.
+ // TODO: remove this once getCountryIso() is fixed to always
+ // return uppercase.
+ countryIso = countryIso.toUpperCase();
+
PhoneNumber pn = null;
try {
+ if (VDBG) Log.v(TAG, "parsing '" + number
+ + "' for countryIso '" + countryIso + "'...");
pn = util.parse(number, countryIso);
if (VDBG) Log.v(TAG, "- parsed number: " + pn);
} catch (NumberParseException e) {
diff --git a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
index 1bd9b0c..2e8a742 100644
--- a/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
+++ b/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java
@@ -55,7 +55,7 @@
// PhoneNumberOfflineGeocoder to look up a "geo description"?
// (TODO: This could become a flag in config.xml if it ever needs to be
// configured on a per-product basis.)
- private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = false;
+ private static final boolean ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION = true;
/**
* Interface for a CallerInfoAsyncQueryHandler result return.
diff --git a/telephony/java/com/android/internal/telephony/DataCallState.java b/telephony/java/com/android/internal/telephony/DataCallState.java
index f5651e0..fba3184 100644
--- a/telephony/java/com/android/internal/telephony/DataCallState.java
+++ b/telephony/java/com/android/internal/telephony/DataCallState.java
@@ -47,6 +47,7 @@
public String [] addresses = new String[0];
public String [] dnses = new String[0];
public String[] gateways = new String[0];
+ public int suggestedRetryTime = -1;
/**
* Class returned by onSetupConnectionCompleted.
@@ -77,6 +78,7 @@
sb.append("DataCallState: {")
.append("version=").append(version)
.append(" status=").append(status)
+ .append(" retry=").append(suggestedRetryTime)
.append(" cid=").append(cid)
.append(" active=").append(active)
.append(" type=").append(type)
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 5c030fd..cb8b0e5 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -182,6 +182,18 @@
}
}
+ public static class CallSetupException extends Exception {
+ private int mRetryOverride = -1;
+
+ CallSetupException (int retryOverride) {
+ mRetryOverride = retryOverride;
+ }
+
+ public int getRetryOverride() {
+ return mRetryOverride;
+ }
+ }
+
// ***** Event codes for driving the state machine
protected static final int BASE = Protocol.BASE_DATA_CONNECTION;
protected static final int EVENT_CONNECT = BASE + 0;
@@ -205,6 +217,7 @@
protected long createTime;
protected long lastFailTime;
protected FailCause lastFailCause;
+ protected int mRetryOverride = -1;
protected static final String NULL_IP = "0.0.0.0";
private int mRefCount;
Object userData;
@@ -288,7 +301,8 @@
} else {
lastFailCause = cause;
lastFailTime = timeStamp;
- AsyncResult.forMessage(connectionCompletedMsg, cause, new Exception());
+ AsyncResult.forMessage(connectionCompletedMsg, cause,
+ new CallSetupException(mRetryOverride));
}
if (DBG) log("notifyConnectionCompleted at " + timeStamp + " cause=" + cause);
@@ -430,6 +444,7 @@
createTime = -1;
lastFailTime = -1;
lastFailCause = FailCause.NONE;
+ mRetryOverride = -1;
mRefCount = 0;
mLinkProperties = new LinkProperties();
@@ -482,6 +497,15 @@
return result;
}
+ private int getSuggestedRetryTime(AsyncResult ar) {
+ int retry = -1;
+ if (ar.exception == null) {
+ DataCallState response = (DataCallState) ar.result;
+ retry = response.suggestedRetryTime;
+ }
+ return retry;
+ }
+
private DataCallState.SetupResult setLinkProperties(DataCallState response,
LinkProperties lp) {
// Check if system property dns usable
@@ -685,10 +709,12 @@
private FailCause mFailCause = null;
private DisconnectParams mDisconnectParams = null;
- public void setEnterNotificationParams(ConnectionParams cp, FailCause cause) {
+ public void setEnterNotificationParams(ConnectionParams cp, FailCause cause,
+ int retryOverride) {
if (VDBG) log("DcInactiveState: setEnterNoticationParams cp,cause");
mConnectionParams = cp;
mFailCause = cause;
+ mRetryOverride = retryOverride;
}
public void setEnterNotificationParams(DisconnectParams dp) {
@@ -808,7 +834,7 @@
// Vendor ril rejected the command and didn't connect.
// Transition to inactive but send notifications after
// we've entered the mInactive state.
- mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
+ mInactiveState.setEnterNotificationParams(cp, result.mFailCause, -1);
transitionTo(mInactiveState);
break;
case ERR_UnacceptableParameter:
@@ -823,7 +849,8 @@
break;
case ERR_RilError:
// Request failed and mFailCause has the reason
- mInactiveState.setEnterNotificationParams(cp, result.mFailCause);
+ mInactiveState.setEnterNotificationParams(cp, result.mFailCause,
+ getSuggestedRetryTime(ar));
transitionTo(mInactiveState);
break;
case ERR_Stale:
@@ -848,8 +875,8 @@
}
// Transition to inactive but send notifications after
// we've entered the mInactive state.
- mInactiveState.setEnterNotificationParams(cp, cause);
- transitionTo(mInactiveState);
+ mInactiveState.setEnterNotificationParams(cp, cause, -1);
+ transitionTo(mInactiveState);
} else {
if (DBG) {
log("DcActivatingState EVENT_GET_LAST_FAIL_DONE is stale cp.tag="
@@ -1016,7 +1043,7 @@
// Transition to inactive but send notifications after
// we've entered the mInactive state.
mInactiveState.setEnterNotificationParams(cp,
- FailCause.UNACCEPTABLE_NETWORK_PARAMETER);
+ FailCause.UNACCEPTABLE_NETWORK_PARAMETER, -1);
transitionTo(mInactiveState);
} else {
if (DBG) {
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionAc.java b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
index 62b90ae..e23f1cc 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionAc.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionAc.java
@@ -24,12 +24,18 @@
import android.net.ProxyProperties;
import android.os.Message;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
/**
* AsyncChannel to a DataConnection
*/
public class DataConnectionAc extends AsyncChannel {
private static final boolean DBG = false;
private String mLogTag;
+ private List<ApnContext> mApnList = null;
public DataConnection dataConnection;
@@ -85,6 +91,7 @@
public DataConnectionAc(DataConnection dc, String logTag) {
dataConnection = dc;
mLogTag = logTag;
+ mApnList = Collections.synchronizedList(new ArrayList<ApnContext>());
}
/**
@@ -371,6 +378,35 @@
}
}
+ /**
+ * Add ApnContext association.
+ *
+ * @param ApnContext to associate
+ */
+ public void addApnContext(ApnContext apnContext) {
+ if (!mApnList.contains(apnContext)) {
+ mApnList.add(apnContext);
+ }
+ }
+
+ /**
+ * Remove ApnContext associateion.
+ *
+ * @param ApnContext to dissociate
+ */
+ public void removeApnContext(ApnContext apnContext) {
+ mApnList.remove(apnContext);
+ }
+
+ /**
+ * Retrieve collection of ApnContext currently associated with the DataConnectionAc.
+ *
+ * @return Collection of ApnContext
+ */
+ public Collection<ApnContext> getApnList() {
+ return mApnList;
+ }
+
private void log(String s) {
android.util.Log.d(mLogTag, "DataConnectionAc " + s);
}
diff --git a/telephony/java/com/android/internal/telephony/RIL.java b/telephony/java/com/android/internal/telephony/RIL.java
index 76f1ab7..c6ed405 100644
--- a/telephony/java/com/android/internal/telephony/RIL.java
+++ b/telephony/java/com/android/internal/telephony/RIL.java
@@ -3009,6 +3009,7 @@
}
} else {
dataCall.status = p.readInt();
+ dataCall.suggestedRetryTime = p.readInt();
dataCall.cid = p.readInt();
dataCall.active = p.readInt();
dataCall.type = p.readString();
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
index 6a95b67..a31b704 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMALTEPhone.java
@@ -129,6 +129,12 @@
super.setSystemLocale(language, country, false);
}
+ // return IMSI from USIM as subscriber ID.
+ @Override
+ public String getSubscriberId() {
+ return mIccRecords.getIMSI();
+ }
+
@Override
protected void log(String s) {
if (DBG)
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 19c06f6..1ac012f 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -68,7 +68,9 @@
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.List;
import java.util.Map;
import java.util.HashMap;
@@ -844,9 +846,16 @@
return result;
}
+ private boolean dataConnectionNotInUse(DataConnectionAc dcac) {
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getDataConnectionAc() == dcac) return false;
+ }
+ return true;
+ }
+
private GsmDataConnection findFreeDataConnection() {
for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
- if (dcac.isInactiveSync()) {
+ if (dcac.isInactiveSync() && dataConnectionNotInUse(dcac)) {
log("findFreeDataConnection: found free GsmDataConnection");
return (GsmDataConnection) dcac.dataConnection;
}
@@ -926,7 +935,7 @@
if (refCount == 0) {
configureRetry(dc, apnContext.getApnType());
}
- apnContext.setDataConnectionAc(mDataConnectionAsyncChannels.get(dc.getDataConnectionId()));
+ apnContext.setDataConnectionAc(dcac);
apnContext.setApnSetting(apn);
apnContext.setDataConnection(dc);
}
@@ -969,6 +978,49 @@
}
/**
+ * @param cid Connection id provided from RIL.
+ * @return DataConnectionAc associated with specified cid.
+ */
+ private DataConnectionAc findDataConnectionAcByCid(int cid) {
+ for (DataConnectionAc dcac : mDataConnectionAsyncChannels.values()) {
+ if (dcac.getCidSync() == cid) {
+ return dcac;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param dcacs Collection of DataConnectionAc reported from RIL.
+ * @return List of ApnContext whihc is connected, but does not present in
+ * data connection list reported from RIL.
+ */
+ private List<ApnContext> findApnContextToClean(Collection<DataConnectionAc> dcacs) {
+ if (dcacs == null) return null;
+
+ ArrayList<ApnContext> list = new ArrayList<ApnContext>();
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.getState() == State.CONNECTED) {
+ boolean found = false;
+ for (DataConnectionAc dcac : dcacs) {
+ if (dcac == apnContext.getDataConnectionAc()) {
+ // ApnContext holds the ref to dcac present in data call list.
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // ApnContext does not have dcan reorted in data call list.
+ if (DBG) log("onDataStateChanged(ar): Connected apn not found in the list (" +
+ apnContext.toString() + ")");
+ list.add(apnContext);
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
* @param ar is the result of RIL_REQUEST_DATA_CALL_LIST
* or RIL_UNSOL_DATA_CALL_LIST_CHANGED
*/
@@ -985,91 +1037,103 @@
if (DBG) log("onDataStateChanged(ar): exception; likely radio not available, ignore");
return;
}
+ if (DBG) log("onDataStateChanged(ar): DataCallState size=" + dataCallStates.size());
- // Create a hash map to store the dataCallState of each call id
+ // Create a hash map to store the dataCallState of each DataConnectionAc
// TODO: Depends on how frequent the DATA_CALL_LIST got updated,
// may cache response to reduce comparison.
- HashMap<Integer, DataCallState> response;
- response = new HashMap<Integer, DataCallState>();
- if (DBG) log("onDataStateChanged(ar): DataCallState size=" + dataCallStates.size());
- for (DataCallState dc : dataCallStates) {
- response.put(dc.cid, dc);
- if (DBG) log("onDataStateChanged(ar): " + dc.cid + ": " + dc.toString());
+ HashMap<DataCallState, DataConnectionAc> response;
+ response = new HashMap<DataCallState, DataConnectionAc>();
+ for (DataCallState dataCallState : dataCallStates) {
+ DataConnectionAc dcac = findDataConnectionAcByCid(dataCallState.cid);
+
+ if (dcac != null) response.put(dataCallState, dcac);
}
- // For each connected apn, check if there is a matched active
- // data call state, which has the same link properties.
- if (DBG) log(" ApnContext size=" + mApnContexts.values().size());
- for (ApnContext apnContext : mApnContexts.values()) {
- if (DBG){
- log("onDataStateChanged(ar): " + apnContext.toString());
- if (apnContext.getDataConnection() != null) {
- log("onDataStateChanged(ar): " + apnContext.getDataConnection().toString());
+ // step1: Find a list of "connected" APN which does not have reference to
+ // calls listed in the Data Call List.
+ List<ApnContext> apnsToClear = findApnContextToClean(response.values());
+
+ // step2: Check status of each calls in Data Call List.
+ // Collect list of ApnContext associated with the data call if the link
+ // has to be cleared.
+ for (DataCallState newState : dataCallStates) {
+ DataConnectionAc dcac = response.get(newState);
+
+ // no associated DataConnection found. Ignore.
+ if (dcac == null) continue;
+
+ Collection<ApnContext> apns = dcac.getApnList();
+
+ // filter out ApnContext with "Connected" state.
+ ArrayList<ApnContext> connectedApns = new ArrayList<ApnContext>();
+ for (ApnContext apnContext : apns) {
+ if ((apnContext != null) &&
+ (apnContext.getState() == State.CONNECTED)) {
+ connectedApns.add(apnContext);
}
}
- DataConnectionAc dcac = apnContext.getDataConnectionAc();
- if (dcac == null) {
+
+ // No "Connected" ApnContext associated with this CID. Ignore.
+ if (connectedApns.isEmpty()) {
continue;
}
- int connectionId = dcac.getCidSync();
- if (apnContext.getState() == State.CONNECTED) {
- // The way things are supposed to work, the PDP list
- // should not contain the CID after it disconnects.
- // However, the way things really work, sometimes the PDP
- // context is still listed with active = false, which
- // makes it hard to distinguish an activating context from
- // an activated-and-then de-activated one.
- if (response.containsKey(connectionId)) {
- DataCallState newState = response.get(connectionId);
- if (DBG) log("onDataStateChanged(ar): Found ConnId=" + connectionId
+ if (DBG) log("onDataStateChanged(ar): Found ConnId=" + newState.cid
+ " newState=" + newState.toString());
- if (newState.active != 0) {
- boolean resetConnection;
- switch (dcac.updateLinkPropertiesDataCallStateSync(newState)) {
- case NONE:
- if (DBG) log("onDataStateChanged(ar): Found but no change, skip");
- resetConnection = false;
- break;
- case CHANGED:
- if (DBG) log("onDataStateChanged(ar): Found and changed, notify");
- mPhone.notifyDataConnection(Phone.REASON_LINK_PROPERTIES_CHANGED,
- apnContext.getApnType());
- // Temporary hack, at this time a transition from CDMA -> Global
- // fails so we'll hope for the best and not reset the connection.
- // @see bug/4455071
- if (SystemProperties.getBoolean("telephony.ignore-state-changes",
- true)) {
- log("onDataStateChanged(ar): STOPSHIP don't reset, continue");
- resetConnection = false;
- } else {
- // Things changed so reset connection, when hack is removed
- // this is the normal path.
- log("onDataStateChanged(ar): changed so resetting connection");
- resetConnection = true;
- }
- break;
- case RESET:
- default:
- if (DBG) log("onDataStateChanged(ar): an error, reset connection");
- resetConnection = true;
- break;
- }
- if (resetConnection == false) continue;
+ if (newState.active != 0) {
+ boolean resetConnection;
+ switch (dcac.updateLinkPropertiesDataCallStateSync(newState)) {
+ case NONE:
+ if (DBG) log("onDataStateChanged(ar): Found but no change, skip");
+ resetConnection = false;
+ break;
+ case CHANGED:
+ for (ApnContext apnContext : connectedApns) {
+ if (DBG) log("onDataStateChanged(ar): Found and changed, notify (" +
+ apnContext.toString() + ")");
+ mPhone.notifyDataConnection(Phone.REASON_LINK_PROPERTIES_CHANGED,
+ apnContext.getApnType());
}
+ // Temporary hack, at this time a transition from CDMA -> Global
+ // fails so we'll hope for the best and not reset the connection.
+ // @see bug/4455071
+ if (SystemProperties.getBoolean("telephony.ignore-state-changes",
+ true)) {
+ log("onDataStateChanged(ar): STOPSHIP don't reset, continue");
+ resetConnection = false;
+ } else {
+ // Things changed so reset connection, when hack is removed
+ // this is the normal path.
+ log("onDataStateChanged(ar): changed so resetting connection");
+ resetConnection = true;
+ }
+ break;
+ case RESET:
+ default:
+ if (DBG) log("onDataStateChanged(ar): an error, reset connection");
+ resetConnection = true;
+ break;
}
-
- if (DBG) log("onDataStateChanged(ar): reset connection.");
-
- // Add an event log when the network drops PDP
- int cid = getCellLocationId();
- EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
- TelephonyManager.getDefault().getNetworkType());
-
- cleanUpConnection(true, apnContext);
+ if (resetConnection == false) continue;
}
+
+ if (DBG) log("onDataStateChanged(ar): reset connection.");
+
+ apnsToClear.addAll(connectedApns);
}
+ // step3: Clear apn connection if applicable.
+ if (!apnsToClear.isEmpty()) {
+ // Add an event log when the network drops PDP
+ int cid = getCellLocationId();
+ EventLog.writeEvent(EventLogTags.PDP_NETWORK_DROP, cid,
+ TelephonyManager.getDefault().getNetworkType());
+ }
+
+ for (ApnContext apnContext : apnsToClear) {
+ cleanUpConnection(true, apnContext);
+ }
if (DBG) log("onDataStateChanged(ar): X");
}
@@ -1332,7 +1396,8 @@
return retry;
}
- private void reconnectAfterFail(FailCause lastFailCauseCode, ApnContext apnContext) {
+ private void reconnectAfterFail(FailCause lastFailCauseCode,
+ ApnContext apnContext, int retryOverride) {
if (apnContext == null) {
loge("reconnectAfterFail: apnContext == null, impossible");
return;
@@ -1357,9 +1422,14 @@
}
}
- int nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();
+ // If retry needs to be backed off for specific case (determined by RIL/Modem)
+ // use the specified timer instead of pre-configured retry pattern.
+ int nextReconnectDelay = retryOverride;
+ if (nextReconnectDelay < 0) {
+ nextReconnectDelay = apnContext.getDataConnection().getRetryTimer();
+ apnContext.getDataConnection().increaseRetryCount();
+ }
startAlarmForReconnect(nextReconnectDelay, apnContext);
- apnContext.getDataConnection().increaseRetryCount();
if (!shouldPostNotification(lastFailCauseCode)) {
if (DBG) {
@@ -1664,7 +1734,13 @@
}
} else {
if (DBG) log("onDataSetupComplete: Not all permanent failures, retry");
- startDelayedRetry(cause, apnContext);
+ // check to see if retry should be overridden for this failure.
+ int retryOverride = -1;
+ if (ar.exception instanceof DataConnection.CallSetupException) {
+ retryOverride =
+ ((DataConnection.CallSetupException)ar.exception).getRetryOverride();
+ }
+ startDelayedRetry(cause, apnContext, retryOverride);
}
} else {
if (DBG) log("onDataSetupComplete: Try next APN");
@@ -1936,9 +2012,10 @@
return result.toString();
}
- private void startDelayedRetry(GsmDataConnection.FailCause cause, ApnContext apnContext) {
+ private void startDelayedRetry(GsmDataConnection.FailCause cause,
+ ApnContext apnContext, int retryOverride) {
notifyNoData(cause, apnContext);
- reconnectAfterFail(cause, apnContext);
+ reconnectAfterFail(cause, apnContext, retryOverride);
}
private void setPreferredApn(int pos) {
diff --git a/tests/BiDiTests/res/drawable/end.png b/tests/BiDiTests/res/drawable/end.png
new file mode 100644
index 0000000..0c110be
--- /dev/null
+++ b/tests/BiDiTests/res/drawable/end.png
Binary files differ
diff --git a/tests/BiDiTests/res/drawable/start.png b/tests/BiDiTests/res/drawable/start.png
new file mode 100644
index 0000000..783bf90
--- /dev/null
+++ b/tests/BiDiTests/res/drawable/start.png
Binary files differ
diff --git a/tests/BiDiTests/res/layout/textview_locale.xml b/tests/BiDiTests/res/layout/textview_locale.xml
new file mode 100644
index 0000000..a2dcbf9
--- /dev/null
+++ b/tests/BiDiTests/res/layout/textview_locale.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textview_locale"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layoutDirection="locale">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+
+ </LinearLayout>
+
+
+</FrameLayout>
diff --git a/tests/BiDiTests/res/layout/textview_ltr.xml b/tests/BiDiTests/res/layout/textview_ltr.xml
new file mode 100644
index 0000000..6e6b976
--- /dev/null
+++ b/tests/BiDiTests/res/layout/textview_ltr.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textview_ltr"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layoutDirection="ltr">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+
+ </LinearLayout>
+
+
+</FrameLayout>
diff --git a/tests/BiDiTests/res/layout/textview_rtl.xml b/tests/BiDiTests/res/layout/textview_rtl.xml
new file mode 100644
index 0000000..117227d
--- /dev/null
+++ b/tests/BiDiTests/res/layout/textview_rtl.xml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textview_rtl"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layoutDirection="rtl">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="start"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="end"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="left"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="right"
+ />
+
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_hebrew_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_latin_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/textview"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:textSize="24dip"
+ android:text="@string/textview_multiline_text"
+ android:minWidth="200dip"
+ android:drawableLeft="@drawable/start"
+ android:drawableRight="@drawable/end"
+ android:paddingLeft="5dip"
+ android:paddingRight="15dip"
+ android:drawablePadding="3dip"
+ android:background="#884"
+ android:layout_marginLeft="5dip"
+ android:layout_marginRight="15dip"
+ android:gravity="center"
+ />
+
+ </LinearLayout>
+
+
+</FrameLayout>
diff --git a/tests/BiDiTests/res/values/strings.xml b/tests/BiDiTests/res/values/strings.xml
index 16dc263..c0bbe94 100644
--- a/tests/BiDiTests/res/values/strings.xml
+++ b/tests/BiDiTests/res/values/strings.xml
@@ -38,4 +38,8 @@
<string name="hebrew_text">םמ</string>
<string name="menu_add">Add</string>
<string name="menu_delete">Delete</string>
-</resources>
\ No newline at end of file
+ <string name="textview_hebrew_text">םמab?!</string>
+ <string name="textview_latin_text">abםמ?!</string>
+ <string name="textview_multiline_text">םמ?!\nab?!</string>
+</resources>
+
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
index bee1881..c2683c2 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -100,8 +100,9 @@
List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
addItem(result, "Basic", BiDiTestBasic.class, R.id.basic);
+
addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas);
-
+
addItem(result, "Linear LTR", BiDiTestLinearLayoutLtr.class, R.id.linear_layout_ltr);
addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl);
addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale);
@@ -121,6 +122,10 @@
addItem(result, "Table RTL", BiDiTestTableLayoutRtl.class, R.id.table_layout_rtl);
addItem(result, "Table LOC", BiDiTestTableLayoutLocale.class, R.id.table_layout_locale);
+ addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr);
+ addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl);
+ addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale);
+
addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding);
addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLocale.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLocale.java
new file mode 100644
index 0000000..6c64755
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLocale.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class BiDiTestTextViewLocale extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.textview_locale, container, false);
+ }
+}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLtr.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLtr.java
new file mode 100644
index 0000000..b168411
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewLtr.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class BiDiTestTextViewLtr extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.textview_ltr, container, false);
+ }
+}
diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewRtl.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewRtl.java
new file mode 100644
index 0000000..6f36ce6
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewRtl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 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.bidi;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class BiDiTestTextViewRtl extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.textview_rtl, container, false);
+ }
+}
diff --git a/tests/GridLayoutTest/res/layout/grid7.xml b/tests/GridLayoutTest/res/layout/grid7.xml
index b97a00b..b9e58d7 100644
--- a/tests/GridLayoutTest/res/layout/grid7.xml
+++ b/tests/GridLayoutTest/res/layout/grid7.xml
@@ -15,56 +15,26 @@
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
-
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="10dip" >
+ android:layout_height="match_parent">
+ android:columnCount="2"
+ <Space
+ android:layout_row="0"
+ android:layout_column="0"
+ android:layout_width="109dip"
+ android:layout_height="108dip"/>
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingRight="5dip"
- android:text="flabe" />
+ <Button
+ android:text="Button 1"
+ android:layout_row="0"
+ android:layout_column="1"
+ />
- <Button android:id="@+id/initialActivity"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="bax" />
- </LinearLayout>
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="5dip" >
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingRight="5dip"
- android:text="bar" />
-
- <EditText android:id="@+id/numberOfEvents"
- android:layout_marginLeft="2dip"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@android:drawable/editbox_background"
- android:numeric="integer"
- android:scrollHorizontally="true"
- android:maxLines="1" />
- </LinearLayout>
-
- <Button android:id="@+id/start"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:paddingTop="10dip"
- android:text="Foo" />
+ <Button
+ android:text="Button 2"
+ android:layout_row="1"
+ android:layout_column="1"
+ />
</GridLayout>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
index 9bb5ba8..723f3e8 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
@@ -85,6 +85,10 @@
}
}
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
private static class RenderThread extends Thread {
private static final String LOG_TAG = "GLTextureView";
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java
index 2e23aaa..f420fa0 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GetBitmapActivity.java
@@ -100,4 +100,9 @@
mCamera.stopPreview();
mCamera.release();
}
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Ignored
+ }
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
index fa2e39a..01ee90a 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -18,7 +18,6 @@
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
@@ -55,7 +54,7 @@
@Override
public void onClick(View v) {
if (mAdded) {
- mAnimatorSet.cancel();
+ if (mAnimatorSet != null) mAnimatorSet.cancel();
mContent.removeView(mTextureView);
} else {
mContent.addView(mTextureView);
@@ -85,25 +84,19 @@
mTextureView.setCameraDistance(5000);
- ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
- rotationY.setRepeatMode(ObjectAnimator.REVERSE);
- rotationY.setRepeatCount(ObjectAnimator.INFINITE);
- rotationY.setDuration(4000);
- rotationY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- ((View) mTextureView.getParent()).invalidate();
- }
- });
+// ObjectAnimator rotationY = ObjectAnimator.ofFloat(mTextureView, "rotationY", 0.0f, 360.0f);
+// rotationY.setRepeatMode(ObjectAnimator.REVERSE);
+// rotationY.setRepeatCount(ObjectAnimator.INFINITE);
+// rotationY.setDuration(4000);
- ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f);
- alpha.setRepeatMode(ObjectAnimator.REVERSE);
- alpha.setRepeatCount(ObjectAnimator.INFINITE);
- alpha.setDuration(4000);
+// ObjectAnimator alpha = ObjectAnimator.ofFloat(mTextureView, "alpha", 1.0f, 0.0f);
+// alpha.setRepeatMode(ObjectAnimator.REVERSE);
+// alpha.setRepeatCount(ObjectAnimator.INFINITE);
+// alpha.setDuration(4000);
- mAnimatorSet = new AnimatorSet();
- mAnimatorSet.play(alpha).with(rotationY);
- mAnimatorSet.start();
+// mAnimatorSet = new AnimatorSet();
+// mAnimatorSet.play(alpha).with(rotationY);
+// mAnimatorSet.start();
}
@Override
@@ -116,4 +109,9 @@
mCamera.stopPreview();
mCamera.release();
}
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Ignored
+ }
}
diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
index 3f57799..c706286 100644
--- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
+++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/RsBenchRS.java
@@ -100,6 +100,7 @@
private ProgramVertex mProgVertex;
private ProgramVertexFixedFunction.Constants mPVA;
+ private ProgramVertexFixedFunction.Constants mPvProjectionAlloc;
// Custom shaders
private ProgramVertex mProgVertexCustom;
@@ -388,6 +389,7 @@
i.Proj = projNorm;
i.MVP = projNorm;
mPvStarAlloc.set(i, 0, true);
+ mPvProjectionAlloc.setProjection(projNorm);
}
private void initProgramVertex() {
@@ -405,8 +407,14 @@
// For galaxy live wallpaper
mPvStarAlloc = new ScriptField_VpConsts(mRS, 1);
mScript.bind_vpConstants(mPvStarAlloc);
+ mPvProjectionAlloc = new ProgramVertexFixedFunction.Constants(mRS);
updateProjectionMatrices();
+ pvb = new ProgramVertexFixedFunction.Builder(mRS);
+ ProgramVertex pvbp = pvb.create();
+ ((ProgramVertexFixedFunction)pvbp).bindConstants(mPvProjectionAlloc);
+ mScript.set_gPVBkProj(pvbp);
+
ProgramVertex.Builder sb = new ProgramVertex.Builder(mRS);
String t = "varying vec4 varColor;\n" +
"varying vec2 varTex0;\n" +
@@ -648,10 +656,10 @@
prepareTestData();
initSamplers();
- initProgramStore();
- initProgramFragment();
initMesh();
initProgramVertex();
+ initProgramStore();
+ initProgramFragment();
initFonts();
loadImages();
initProgramRaster();
@@ -674,7 +682,6 @@
Allocation.USAGE_GRAPHICS_RENDER_TARGET);
mScript.set_gRenderBufferDepth(offscreen);
-
mTextureAllocs = new ScriptField_ListAllocs_s(mRS, 100);
for (int i = 0; i < 100; i++) {
ScriptField_ListAllocs_s.Item texElem = new ScriptField_ListAllocs_s.Item();
diff --git a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
index 6d80b0e..bb81862 100644
--- a/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
+++ b/tests/RenderScriptTests/PerfTest/src/com/android/perftest/rsbench.rs
@@ -393,19 +393,28 @@
rsgBindProgramStore(gProgStoreBlendAlpha);
rsgBindProgramFragment(gProgFragmentTexture);
rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
+ rsgBindTexture(gProgFragmentTexture, 0, gTexTorus);
+ rsgDrawQuadTexCoords(
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, gRenderSurfaceH, 0.0f, 0.0f, 1.0f,
+ gRenderSurfaceW, gRenderSurfaceH, 0.0f, 1.0f, 1.0f,
+ gRenderSurfaceW, 0.0f, 0.0f, 1.0f, 0.0f);
int meshCount = (int)pow(10.0f, (float)(meshMode + 1));
- float size = 50.0;
+ float wSize = gRenderSurfaceW/(float)meshCount;
+ float hSize = gRenderSurfaceH/(float)meshCount;
rs_matrix4x4 matrix;
- rsMatrixLoadScale(&matrix, size, size, 1.0);
+ rsMatrixLoadScale(&matrix, wSize, hSize, 1.0);
float yPos = 0;
+ float yPad = hSize / 2;
+ float xPad = wSize / 2;
for (int y = 0; y < meshCount; y++) {
- yPos = (y + 1) * 50;
+ yPos = y * hSize + yPad;
float xPos = 0;
for (int x = 0; x < meshCount; x++) {
- xPos = (x + 1) * 50;
+ xPos = x * wSize + xPad;
rs_matrix4x4 transMatrix;
rsMatrixLoadTranslate(&transMatrix, xPos, yPos, 0);
rsMatrixMultiply(&transMatrix, &matrix);
@@ -529,11 +538,12 @@
}
// Display images and text with live wallpaper in the background
-static void dispalyLiveWallPaper(int wResolution, int hResolution) {
+static void displayLiveWallPaper(int wResolution, int hResolution) {
bindProgramVertexOrtho();
drawGalaxy();
+ rsgBindProgramVertex(gProgVertex);
rsgBindProgramStore(gProgStoreBlendAlpha);
rsgBindProgramFragment(gProgFragmentTexture);
rsgBindSampler(gProgFragmentTexture, 0, gLinearClamp);
@@ -958,7 +968,7 @@
displayListView();
break;
case 30:
- dispalyLiveWallPaper(7, 5);
+ displayLiveWallPaper(7, 5);
break;
}
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTest.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTest.java
index 89fc34b..d1b23fa 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTest.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTest.java
@@ -39,7 +39,7 @@
public class RSTest extends Activity {
//EventListener mListener = new EventListener();
- private static final String LOG_TAG = "libRS_jni";
+ private static final String LOG_TAG = "RSTest";
private static final boolean DEBUG = false;
private static final boolean LOG_ENABLED = false;
@@ -73,6 +73,14 @@
mView.pause();
}
+ @Override
+ protected void onStop() {
+ // Actually kill the app if we are stopping. We don't want to
+ // continue/resume this test ever. It should always start fresh.
+ finish();
+ super.onStop();
+ }
+
static void log(String message) {
if (LOG_ENABLED) {
Log.v(LOG_TAG, message);
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
index 541bf22..e77998e 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/RSTestCore.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2008-2011 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.
@@ -65,6 +65,7 @@
unitTests = new ArrayList<UnitTest>();
unitTests.add(new UT_primitives(this, mRes, mCtx));
+ unitTests.add(new UT_vector(this, mRes, mCtx));
unitTests.add(new UT_rsdebug(this, mRes, mCtx));
unitTests.add(new UT_rstime(this, mRes, mCtx));
unitTests.add(new UT_rstypes(this, mRes, mCtx));
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java
new file mode 100644
index 0000000..748701d
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UT_vector.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2011 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.rs.test;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.renderscript.*;
+
+public class UT_vector extends UnitTest {
+ private Resources mRes;
+
+ protected UT_vector(RSTestCore rstc, Resources res, Context ctx) {
+ super(rstc, "Vector", ctx);
+ mRes = res;
+ }
+
+ private boolean initializeGlobals(ScriptC_vector s) {
+ Float2 F2 = s.get_f2();
+ if (F2.x != 1.0f || F2.y != 2.0f) {
+ return false;
+ }
+ F2.x = 2.99f;
+ F2.y = 3.99f;
+ s.set_f2(F2);
+
+ Float3 F3 = s.get_f3();
+ if (F3.x != 1.0f || F3.y != 2.0f || F3.z != 3.0f) {
+ return false;
+ }
+ F3.x = 2.99f;
+ F3.y = 3.99f;
+ F3.z = 4.99f;
+ s.set_f3(F3);
+
+ Float4 F4 = s.get_f4();
+ if (F4.x != 1.0f || F4.y != 2.0f || F4.z != 3.0f || F4.w != 4.0f) {
+ return false;
+ }
+ F4.x = 2.99f;
+ F4.y = 3.99f;
+ F4.z = 4.99f;
+ F4.w = 5.99f;
+ s.set_f4(F4);
+
+ Double2 D2 = s.get_d2();
+ if (D2.x != 1.0 || D2.y != 2.0) {
+ return false;
+ }
+ D2.x = 2.99;
+ D2.y = 3.99;
+ s.set_d2(D2);
+
+ Double3 D3 = s.get_d3();
+ if (D3.x != 1.0 || D3.y != 2.0 || D3.z != 3.0) {
+ return false;
+ }
+ D3.x = 2.99;
+ D3.y = 3.99;
+ D3.z = 4.99;
+ s.set_d3(D3);
+
+ Double4 D4 = s.get_d4();
+ if (D4.x != 1.0 || D4.y != 2.0 || D4.z != 3.0 || D4.w != 4.0) {
+ return false;
+ }
+ D4.x = 2.99;
+ D4.y = 3.99;
+ D4.z = 4.99;
+ D4.w = 5.99;
+ s.set_d4(D4);
+
+ Byte2 B2 = s.get_i8_2();
+ if (B2.x != 1 || B2.y != 2) {
+ return false;
+ }
+ B2.x = 2;
+ B2.y = 3;
+ s.set_i8_2(B2);
+
+ Byte3 B3 = s.get_i8_3();
+ if (B3.x != 1 || B3.y != 2 || B3.z != 3) {
+ return false;
+ }
+ B3.x = 2;
+ B3.y = 3;
+ B3.z = 4;
+ s.set_i8_3(B3);
+
+ Byte4 B4 = s.get_i8_4();
+ if (B4.x != 1 || B4.y != 2 || B4.z != 3 || B4.w != 4) {
+ return false;
+ }
+ B4.x = 2;
+ B4.y = 3;
+ B4.z = 4;
+ B4.w = 5;
+ s.set_i8_4(B4);
+
+ Short2 S2 = s.get_u8_2();
+ if (S2.x != 1 || S2.y != 2) {
+ return false;
+ }
+ S2.x = 2;
+ S2.y = 3;
+ s.set_u8_2(S2);
+
+ Short3 S3 = s.get_u8_3();
+ if (S3.x != 1 || S3.y != 2 || S3.z != 3) {
+ return false;
+ }
+ S3.x = 2;
+ S3.y = 3;
+ S3.z = 4;
+ s.set_u8_3(S3);
+
+ Short4 S4 = s.get_u8_4();
+ if (S4.x != 1 || S4.y != 2 || S4.z != 3 || S4.w != 4) {
+ return false;
+ }
+ S4.x = 2;
+ S4.y = 3;
+ S4.z = 4;
+ S4.w = 5;
+ s.set_u8_4(S4);
+
+ S2 = s.get_i16_2();
+ if (S2.x != 1 || S2.y != 2) {
+ return false;
+ }
+ S2.x = 2;
+ S2.y = 3;
+ s.set_i16_2(S2);
+
+ S3 = s.get_i16_3();
+ if (S3.x != 1 || S3.y != 2 || S3.z != 3) {
+ return false;
+ }
+ S3.x = 2;
+ S3.y = 3;
+ S3.z = 4;
+ s.set_i16_3(S3);
+
+ S4 = s.get_i16_4();
+ if (S4.x != 1 || S4.y != 2 || S4.z != 3 || S4.w != 4) {
+ return false;
+ }
+ S4.x = 2;
+ S4.y = 3;
+ S4.z = 4;
+ S4.w = 5;
+ s.set_i16_4(S4);
+
+ Int2 I2 = s.get_u16_2();
+ if (I2.x != 1 || I2.y != 2) {
+ return false;
+ }
+ I2.x = 2;
+ I2.y = 3;
+ s.set_u16_2(I2);
+
+ Int3 I3 = s.get_u16_3();
+ if (I3.x != 1 || I3.y != 2 || I3.z != 3) {
+ return false;
+ }
+ I3.x = 2;
+ I3.y = 3;
+ I3.z = 4;
+ s.set_u16_3(I3);
+
+ Int4 I4 = s.get_u16_4();
+ if (I4.x != 1 || I4.y != 2 || I4.z != 3 || I4.w != 4) {
+ return false;
+ }
+ I4.x = 2;
+ I4.y = 3;
+ I4.z = 4;
+ I4.w = 5;
+ s.set_u16_4(I4);
+
+ I2 = s.get_i32_2();
+ if (I2.x != 1 || I2.y != 2) {
+ return false;
+ }
+ I2.x = 2;
+ I2.y = 3;
+ s.set_i32_2(I2);
+
+ I3 = s.get_i32_3();
+ if (I3.x != 1 || I3.y != 2 || I3.z != 3) {
+ return false;
+ }
+ I3.x = 2;
+ I3.y = 3;
+ I3.z = 4;
+ s.set_i32_3(I3);
+
+ I4 = s.get_i32_4();
+ if (I4.x != 1 || I4.y != 2 || I4.z != 3 || I4.w != 4) {
+ return false;
+ }
+ I4.x = 2;
+ I4.y = 3;
+ I4.z = 4;
+ I4.w = 5;
+ s.set_i32_4(I4);
+
+ Long2 L2 = s.get_u32_2();
+ if (L2.x != 1 || L2.y != 2) {
+ return false;
+ }
+ L2.x = 2;
+ L2.y = 3;
+ s.set_u32_2(L2);
+
+ Long3 L3 = s.get_u32_3();
+ if (L3.x != 1 || L3.y != 2 || L3.z != 3) {
+ return false;
+ }
+ L3.x = 2;
+ L3.y = 3;
+ L3.z = 4;
+ s.set_u32_3(L3);
+
+ Long4 L4 = s.get_u32_4();
+ if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) {
+ return false;
+ }
+ L4.x = 2;
+ L4.y = 3;
+ L4.z = 4;
+ L4.w = 5;
+ s.set_u32_4(L4);
+
+ L2 = s.get_i64_2();
+ if (L2.x != 1 || L2.y != 2) {
+ return false;
+ }
+ L2.x = 2;
+ L2.y = 3;
+ s.set_i64_2(L2);
+
+ L3 = s.get_i64_3();
+ if (L3.x != 1 || L3.y != 2 || L3.z != 3) {
+ return false;
+ }
+ L3.x = 2;
+ L3.y = 3;
+ L3.z = 4;
+ s.set_i64_3(L3);
+
+ L4 = s.get_i64_4();
+ if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) {
+ return false;
+ }
+ L4.x = 2;
+ L4.y = 3;
+ L4.z = 4;
+ L4.w = 5;
+ s.set_i64_4(L4);
+
+ L2 = s.get_u64_2();
+ if (L2.x != 1 || L2.y != 2) {
+ return false;
+ }
+ L2.x = 2;
+ L2.y = 3;
+ s.set_u64_2(L2);
+
+ L3 = s.get_u64_3();
+ if (L3.x != 1 || L3.y != 2 || L3.z != 3) {
+ return false;
+ }
+ L3.x = 2;
+ L3.y = 3;
+ L3.z = 4;
+ s.set_u64_3(L3);
+
+ L4 = s.get_u64_4();
+ if (L4.x != 1 || L4.y != 2 || L4.z != 3 || L4.w != 4) {
+ return false;
+ }
+ L4.x = 2;
+ L4.y = 3;
+ L4.z = 4;
+ L4.w = 5;
+ s.set_u64_4(L4);
+
+ return true;
+ }
+
+ public void run() {
+ RenderScript pRS = RenderScript.create(mCtx);
+ ScriptC_vector s = new ScriptC_vector(pRS, mRes, R.raw.vector);
+ pRS.setMessageHandler(mRsMessage);
+ if (!initializeGlobals(s)) {
+ result = -1;
+ } else {
+ s.invoke_vector_test();
+ pRS.finish();
+ waitForMessage();
+ }
+ pRS.destroy();
+ }
+}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
index a7722c7..6151431 100644
--- a/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/UnitTest.java
@@ -17,7 +17,6 @@
package com.android.rs.test;
import android.content.Context;
import android.renderscript.RenderScript.RSMessageHandler;
-import android.util.Log;
public class UnitTest extends Thread {
public String name;
@@ -67,7 +66,7 @@
result = -1;
break;
default:
- android.util.Log.v("RenderScript", "Unit test got unexpected message");
+ RSTest.log("Unit test got unexpected message");
return;
}
}
diff --git a/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs b/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs
new file mode 100644
index 0000000..0430a2f
--- /dev/null
+++ b/tests/RenderScriptTests/tests/src/com/android/rs/test/vector.rs
@@ -0,0 +1,198 @@
+#include "shared.rsh"
+
+// Testing vector types
+float2 f2 = { 1.0f, 2.0f };
+float3 f3 = { 1.0f, 2.0f, 3.0f };
+float4 f4 = { 1.0f, 2.0f, 3.0f, 4.0f };
+
+double2 d2 = { 1.0, 2.0 };
+double3 d3 = { 1.0, 2.0, 3.0 };
+double4 d4 = { 1.0, 2.0, 3.0, 4.0 };
+
+char2 i8_2 = { 1, 2 };
+char3 i8_3 = { 1, 2, 3 };
+char4 i8_4 = { 1, 2, 3, 4 };
+
+uchar2 u8_2 = { 1, 2 };
+uchar3 u8_3 = { 1, 2, 3 };
+uchar4 u8_4 = { 1, 2, 3, 4 };
+
+short2 i16_2 = { 1, 2 };
+short3 i16_3 = { 1, 2, 3 };
+short4 i16_4 = { 1, 2, 3, 4 };
+
+ushort2 u16_2 = { 1, 2 };
+ushort3 u16_3 = { 1, 2, 3 };
+ushort4 u16_4 = { 1, 2, 3, 4 };
+
+int2 i32_2 = { 1, 2 };
+int3 i32_3 = { 1, 2, 3 };
+int4 i32_4 = { 1, 2, 3, 4 };
+
+uint2 u32_2 = { 1, 2 };
+uint3 u32_3 = { 1, 2, 3 };
+uint4 u32_4 = { 1, 2, 3, 4 };
+
+long2 i64_2 = { 1, 2 };
+long3 i64_3 = { 1, 2, 3 };
+long4 i64_4 = { 1, 2, 3, 4 };
+
+ulong2 u64_2 = { 1, 2 };
+ulong3 u64_3 = { 1, 2, 3 };
+ulong4 u64_4 = { 1, 2, 3, 4 };
+
+static bool test_vector_types() {
+ bool failed = false;
+
+ rsDebug("Testing F32", 0);
+ _RS_ASSERT(f2.x == 2.99f);
+ _RS_ASSERT(f2.y == 3.99f);
+
+ _RS_ASSERT(f3.x == 2.99f);
+ _RS_ASSERT(f3.y == 3.99f);
+ _RS_ASSERT(f3.z == 4.99f);
+
+ _RS_ASSERT(f4.x == 2.99f);
+ _RS_ASSERT(f4.y == 3.99f);
+ _RS_ASSERT(f4.z == 4.99f);
+ _RS_ASSERT(f4.w == 5.99f);
+
+ rsDebug("Testing F64", 0);
+ _RS_ASSERT(d2.x == 2.99);
+ _RS_ASSERT(d2.y == 3.99);
+
+ _RS_ASSERT(d3.x == 2.99);
+ _RS_ASSERT(d3.y == 3.99);
+ _RS_ASSERT(d3.z == 4.99);
+
+ _RS_ASSERT(d4.x == 2.99);
+ _RS_ASSERT(d4.y == 3.99);
+ _RS_ASSERT(d4.z == 4.99);
+ _RS_ASSERT(d4.w == 5.99);
+
+ rsDebug("Testing I8", 0);
+ _RS_ASSERT(i8_2.x == 2);
+ _RS_ASSERT(i8_2.y == 3);
+
+ _RS_ASSERT(i8_3.x == 2);
+ _RS_ASSERT(i8_3.y == 3);
+ _RS_ASSERT(i8_3.z == 4);
+
+ _RS_ASSERT(i8_4.x == 2);
+ _RS_ASSERT(i8_4.y == 3);
+ _RS_ASSERT(i8_4.z == 4);
+ _RS_ASSERT(i8_4.w == 5);
+
+ rsDebug("Testing U8", 0);
+ _RS_ASSERT(u8_2.x == 2);
+ _RS_ASSERT(u8_2.y == 3);
+
+ _RS_ASSERT(u8_3.x == 2);
+ _RS_ASSERT(u8_3.y == 3);
+ _RS_ASSERT(u8_3.z == 4);
+
+ _RS_ASSERT(u8_4.x == 2);
+ _RS_ASSERT(u8_4.y == 3);
+ _RS_ASSERT(u8_4.z == 4);
+ _RS_ASSERT(u8_4.w == 5);
+
+ rsDebug("Testing I16", 0);
+ _RS_ASSERT(i16_2.x == 2);
+ _RS_ASSERT(i16_2.y == 3);
+
+ _RS_ASSERT(i16_3.x == 2);
+ _RS_ASSERT(i16_3.y == 3);
+ _RS_ASSERT(i16_3.z == 4);
+
+ _RS_ASSERT(i16_4.x == 2);
+ _RS_ASSERT(i16_4.y == 3);
+ _RS_ASSERT(i16_4.z == 4);
+ _RS_ASSERT(i16_4.w == 5);
+
+ rsDebug("Testing U16", 0);
+ _RS_ASSERT(u16_2.x == 2);
+ _RS_ASSERT(u16_2.y == 3);
+
+ _RS_ASSERT(u16_3.x == 2);
+ _RS_ASSERT(u16_3.y == 3);
+ _RS_ASSERT(u16_3.z == 4);
+
+ _RS_ASSERT(u16_4.x == 2);
+ _RS_ASSERT(u16_4.y == 3);
+ _RS_ASSERT(u16_4.z == 4);
+ _RS_ASSERT(u16_4.w == 5);
+
+ rsDebug("Testing I32", 0);
+ _RS_ASSERT(i32_2.x == 2);
+ _RS_ASSERT(i32_2.y == 3);
+
+ _RS_ASSERT(i32_3.x == 2);
+ _RS_ASSERT(i32_3.y == 3);
+ _RS_ASSERT(i32_3.z == 4);
+
+ _RS_ASSERT(i32_4.x == 2);
+ _RS_ASSERT(i32_4.y == 3);
+ _RS_ASSERT(i32_4.z == 4);
+ _RS_ASSERT(i32_4.w == 5);
+
+ rsDebug("Testing U32", 0);
+ _RS_ASSERT(u32_2.x == 2);
+ _RS_ASSERT(u32_2.y == 3);
+
+ _RS_ASSERT(u32_3.x == 2);
+ _RS_ASSERT(u32_3.y == 3);
+ _RS_ASSERT(u32_3.z == 4);
+
+ _RS_ASSERT(u32_4.x == 2);
+ _RS_ASSERT(u32_4.y == 3);
+ _RS_ASSERT(u32_4.z == 4);
+ _RS_ASSERT(u32_4.w == 5);
+
+ rsDebug("Testing I64", 0);
+ _RS_ASSERT(i64_2.x == 2);
+ _RS_ASSERT(i64_2.y == 3);
+
+ _RS_ASSERT(i64_3.x == 2);
+ _RS_ASSERT(i64_3.y == 3);
+ _RS_ASSERT(i64_3.z == 4);
+
+ _RS_ASSERT(i64_4.x == 2);
+ _RS_ASSERT(i64_4.y == 3);
+ _RS_ASSERT(i64_4.z == 4);
+ _RS_ASSERT(i64_4.w == 5);
+
+ rsDebug("Testing U64", 0);
+ _RS_ASSERT(u64_2.x == 2);
+ _RS_ASSERT(u64_2.y == 3);
+
+ _RS_ASSERT(u64_3.x == 2);
+ _RS_ASSERT(u64_3.y == 3);
+ _RS_ASSERT(u64_3.z == 4);
+
+ _RS_ASSERT(u64_4.x == 2);
+ _RS_ASSERT(u64_4.y == 3);
+ _RS_ASSERT(u64_4.z == 4);
+ _RS_ASSERT(u64_4.w == 5);
+
+ if (failed) {
+ rsDebug("test_vector FAILED", 0);
+ }
+ else {
+ rsDebug("test_vector PASSED", 0);
+ }
+
+ return failed;
+}
+
+void vector_test() {
+ bool failed = false;
+ failed |= test_vector_types();
+
+ if (failed) {
+ rsSendToClientBlocking(RS_MSG_TEST_FAILED);
+ }
+ else {
+ rsSendToClientBlocking(RS_MSG_TEST_PASSED);
+ }
+}
+
diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
index a45e879..fb2fc85 100644
--- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
+++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java
@@ -190,7 +190,6 @@
// Used by DecimalFormatSymbols.
result.zeroDigit = '0';
- result.digit = '0';
result.decimalSeparator = '.';
result.groupingSeparator = ',';
result.patternSeparator = ' ';
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index 65a64cd..894611b 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -30,9 +30,9 @@
Consequently this tool:
- parses the input JAR,
- modifies some of the classes directly using some bytecode manipulation,
-- filters some packages and removes some that we don't want to end in the output JAR,
+- filters some packages and removes those we don't want in the output JAR,
- injects some new classes,
-- and generates a modified JAR file that is suitable for the Android plugin
+- generates a modified JAR file that is suitable for the Android plugin
for Eclipse to perform rendering.
The ASM library is used to do the bytecode modification using its visitor pattern API.
@@ -63,7 +63,7 @@
To do that, the analyzer is created with a list of base classes to keep -- everything
that derives from these is kept. Currently the one such class is android.view.View:
-since we want to render layouts, anything that is sort of the view needs to be kept.
+since we want to render layouts, anything that is sort of a view needs to be kept.
The analyzer is also given a list of class names to keep in the output.
This is done using shell-like glob patterns that filter on the fully-qualified
@@ -90,6 +90,7 @@
- the classes to inject in the output JAR -- these classes are directly implemented
in layoutlib_create and will be used to interface with the renderer in Eclipse.
- specific methods to override (see method stubs details below).
+- specific methods for which to delegate calls.
- specific methods to remove based on their return type.
- specific classes to rename.
@@ -114,6 +115,9 @@
The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub.
+The transformed class is then fed through the DelegateClassAdapter to implement
+method delegates.
+
Finally fields are also visited and changed from protected/private to public.
@@ -131,8 +135,7 @@
method was native. We do not currently provide the parameters. The listener
can however specify the return value of the overridden method.
-An extension being worked on is to actually replace these listeners by
-direct calls to a delegate class, complete with parameters.
+This strategy is now obsolete and replaced by the method delegates.
- Strategies
@@ -160,6 +163,9 @@
by a call to a specific OveriddeMethod.invokeX(). The bridge then registers
a listener on the method signature and can provide an implementation.
+This strategy is now obsolete and replaced by the method delegates.
+See strategy 5 below.
+
3- Renaming classes
@@ -195,6 +201,24 @@
bridge will provide its own implementation.
+5- Method Delegates
+
+This strategy is used to override method implementations.
+Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
+a- A copy of the original method named SomeClass.MethodName_Original().
+ The content is the original method as-is from the reader.
+ This step is omitted if the method is native, since it has no Java implementation.
+b- A brand new implementation of SomeClass.MethodName() which calls to a
+ non-existing static method named SomeClass_Delegate.MethodName().
+ The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+The delegate method is a static method.
+If the original method is non-static, the delegate method receives the original 'this'
+as its first argument. If the original method is an inner non-static method, it also
+receives the inner 'this' as the second argument.
+
+
+
- References -
--------------
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 5c60318..233f72ec 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -51,6 +51,8 @@
* Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null.
+ * <p/>
+ * This usage is deprecated. Please use method 'delegates' instead.
*/
public String[] getOverriddenMethods() {
return OVERRIDDEN_METHODS;
@@ -158,6 +160,7 @@
/**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
+ * This usage is deprecated. Please use method 'delegates' instead.
*/
private final static String[] OVERRIDDEN_METHODS = new String[] {
};
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index 9cba8a0..49ddf1d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -31,6 +31,11 @@
*/
public class DelegateClassAdapter extends ClassAdapter {
+ /** Suffix added to original methods. */
+ private static final String ORIGINAL_SUFFIX = "_Original";
+ private static String CONSTRUCTOR = "<init>";
+ private static String CLASS_INIT = "<clinit>";
+
public final static String ALL_NATIVES = "<<all_natives>>";
private final String mClassName;
@@ -73,22 +78,55 @@
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
mDelegateMethods.contains(name);
- if (useDelegate) {
- // remove native
- access = access & ~Opcodes.ACC_NATIVE;
+ if (!useDelegate) {
+ // Not creating a delegate for this method, pass it as-is from the reader
+ // to the writer.
+ return super.visitMethod(access, name, desc, signature, exceptions);
}
- MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
if (useDelegate) {
- DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
- name, desc, isStatic);
- if (isNative) {
- // A native has no code to visit, so we need to generate it directly.
- a.generateCode();
- } else {
- return a;
+ if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
+ // We don't currently support generating delegates for constructors.
+ throw new UnsupportedOperationException(
+ String.format(
+ "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
+ mClassName, name, desc));
}
}
- return mw;
+
+ if (isNative) {
+ // Remove native flag
+ access = access & ~Opcodes.ACC_NATIVE;
+ MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
+
+ // A native has no code to visit, so we need to generate it directly.
+ a.generateDelegateCode();
+
+ return mwDelegate;
+ }
+
+ // Given a non-native SomeClass.MethodName(), we want to generate 2 methods:
+ // - A copy of the original method named SomeClass.MethodName_Original().
+ // The content is the original method as-is from the reader.
+ // - A brand new implementation of SomeClass.MethodName() which calls to a
+ // non-existing method named SomeClass_Delegate.MethodName().
+ // The implementation of this 'delegate' method is done in layoutlib_brigde.
+
+ int accessDelegate = access;
+ // change access to public for the original one
+ access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
+ access |= Opcodes.ACC_PUBLIC;
+
+ MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
+ desc, signature, exceptions);
+ MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
+ desc, signature, exceptions);
+
+ DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ return a;
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
deleted file mode 100644
index 8d7f016..0000000
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ /dev/null
@@ -1,358 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.tools.layoutlib.create;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.Attribute;
-import org.objectweb.asm.ClassReader;
-import org.objectweb.asm.ClassVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-
-import java.util.ArrayList;
-
-/**
- * This method adapter rewrites a method by discarding the original code and generating
- * a call to a delegate. Original annotations are passed along unchanged.
- * <p/>
- * Calls are delegated to a class named <code><className>_Delegate</code> with
- * static methods matching the methods to be overridden here. The methods have the
- * same return type. The argument type list is the same except the "this" reference is
- * passed first for non-static methods.
- * <p/>
- * A new annotation is added.
- * <p/>
- * Note that native methods have, by definition, no code so there's nothing a visitor
- * can visit. That means the caller must call {@link #generateCode()} directly for
- * a native and use the visitor pattern for non-natives.
- * <p/>
- * Instances of this class are not re-usable. You need a new instance for each method.
- */
-class DelegateMethodAdapter implements MethodVisitor {
-
- /**
- * Suffix added to delegate classes.
- */
- public static final String DELEGATE_SUFFIX = "_Delegate";
-
- private static String CONSTRUCTOR = "<init>";
- private static String CLASS_INIT = "<clinit>";
-
- /** The parent method writer */
- private MethodVisitor mParentVisitor;
- /** Flag to output the first line number. */
- private boolean mOutputFirstLineNumber = true;
- /** The original method descriptor (return type + argument types.) */
- private String mDesc;
- /** True if the original method is static. */
- private final boolean mIsStatic;
- /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
- private final String mClassName;
- /** The method name. */
- private final String mMethodName;
- /** Logger object. */
- private final Log mLog;
- /** True if {@link #visitCode()} has been invoked. */
- private boolean mVisitCodeCalled;
-
- /**
- * Creates a new {@link DelegateMethodAdapter} that will transform this method
- * into a delegate call.
- * <p/>
- * See {@link DelegateMethodAdapter} for more details.
- *
- * @param log The logger object. Must not be null.
- * @param mv the method visitor to which this adapter must delegate calls.
- * @param className The internal class name of the class to visit,
- * e.g. <code>com/android/SomeClass$InnerClass</code>.
- * @param methodName The simple name of the method.
- * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
- * {@link Type#getArgumentTypes(String)})
- * @param isStatic True if the method is declared static.
- */
- public DelegateMethodAdapter(Log log,
- MethodVisitor mv,
- String className,
- String methodName,
- String desc,
- boolean isStatic) {
- mLog = log;
- mParentVisitor = mv;
- mClassName = className;
- mMethodName = methodName;
- mDesc = desc;
- mIsStatic = isStatic;
-
- if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
- // We're going to simplify by not supporting constructors.
- // The only trick with a constructor is to find the proper super constructor
- // and call it (and deciding if we should mirror the original method call to
- // a custom constructor or call a default one.)
- throw new UnsupportedOperationException(
- String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
- className, methodName, desc));
- }
- }
-
- /**
- * Generates the new code for the method.
- * <p/>
- * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
- * (since they have no code to visit).
- * <p/>
- * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
- * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
- * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
- * this method will be invoked from {@link MethodVisitor#visitEnd()}.
- */
- public void generateCode() {
- /*
- * The goal is to generate a call to a static delegate method.
- * If this method is non-static, the first parameter will be 'this'.
- * All the parameters must be passed and then the eventual return type returned.
- *
- * Example, let's say we have a method such as
- * public void method_1(int a, Object b, ArrayList<String> c) { ... }
- *
- * We'll want to create a body that calls a delegate method like this:
- * TheClass_Delegate.method_1(this, a, b, c);
- *
- * If the method is non-static and the class name is an inner class (e.g. has $ in its
- * last segment), we want to push the 'this' of the outer class first:
- * OuterClass_InnerClass_Delegate.method_1(
- * OuterClass.this,
- * OuterClass$InnerClass.this,
- * a, b, c);
- *
- * Only one level of inner class is supported right now, for simplicity and because
- * we don't need more.
- *
- * The generated class name is the current class name with "_Delegate" appended to it.
- * One thing to realize is that we don't care about generics -- since generic types
- * are erased at runtime, they have no influence on the method name being called.
- */
-
- // Add our annotation
- AnnotationVisitor aw = mParentVisitor.visitAnnotation(
- Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
- true); // visible at runtime
- aw.visitEnd();
-
- if (!mVisitCodeCalled) {
- // If this is a direct call to generateCode() as done by DelegateClassAdapter
- // for natives, visitCode() hasn't been called yet.
- mParentVisitor.visitCode();
- mVisitCodeCalled = true;
- }
-
- ArrayList<Type> paramTypes = new ArrayList<Type>();
- String delegateClassName = mClassName + DELEGATE_SUFFIX;
- boolean pushedArg0 = false;
- int maxStack = 0;
-
- // For an instance method (e.g. non-static), push the 'this' preceded
- // by the 'this' of any outer class, if any.
- if (!mIsStatic) {
- // Check if the last segment of the class name has inner an class.
- // Right now we only support one level of inner classes.
- int slash = mClassName.lastIndexOf('/');
- int dol = mClassName.lastIndexOf('$');
- if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
- String outerClass = mClassName.substring(0, dol);
- Type outerType = Type.getObjectType(outerClass);
-
- // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
- delegateClassName = delegateClassName.replace('$', '_');
-
- // The first-level inner class has a package-protected member called 'this$0'
- // that points to the outer class.
-
- // Push this.getField("this$0") on the call stack.
- mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
- mParentVisitor.visitFieldInsn(Opcodes.GETFIELD,
- mClassName, // class where the field is defined
- "this$0", // field name
- outerType.getDescriptor()); // type of the field
- maxStack++;
- paramTypes.add(outerType);
- }
-
- // Push "this" for the instance method, which is always ALOAD 0
- mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
- maxStack++;
- pushedArg0 = true;
- paramTypes.add(Type.getObjectType(mClassName));
- }
-
- // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
- Type[] argTypes = Type.getArgumentTypes(mDesc);
- int maxLocals = pushedArg0 ? 1 : 0;
- for (Type t : argTypes) {
- int size = t.getSize();
- mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
- maxLocals += size;
- maxStack += size;
- paramTypes.add(t);
- }
-
- // Construct the descriptor of the delegate based on the parameters
- // we pushed on the call stack. The return type remains unchanged.
- String desc = Type.getMethodDescriptor(
- Type.getReturnType(mDesc),
- paramTypes.toArray(new Type[paramTypes.size()]));
-
- // Invoke the static delegate
- mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
- delegateClassName,
- mMethodName,
- desc);
-
- Type returnType = Type.getReturnType(mDesc);
- mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
-
- mParentVisitor.visitMaxs(maxStack, maxLocals);
- mParentVisitor.visitEnd();
-
- // For debugging now. Maybe we should collect these and store them in
- // a text file for helping create the delegates. We could also compare
- // the text file to a golden and break the build on unsupported changes
- // or regressions. Even better we could fancy-print something that looks
- // like the expected Java method declaration.
- mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
- }
-
- /* Pass down to visitor writer. In this implementation, either do nothing. */
- public void visitCode() {
- mVisitCodeCalled = true;
- mParentVisitor.visitCode();
- }
-
- /*
- * visitMaxs is called just before visitEnd if there was any code to rewrite.
- * Skip the original.
- */
- public void visitMaxs(int maxStack, int maxLocals) {
- }
-
- /**
- * End of visiting. Generate the messaging code.
- */
- public void visitEnd() {
- generateCode();
- }
-
- /* Writes all annotation from the original method. */
- public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
- return mParentVisitor.visitAnnotation(desc, visible);
- }
-
- /* Writes all annotation default values from the original method. */
- public AnnotationVisitor visitAnnotationDefault() {
- return mParentVisitor.visitAnnotationDefault();
- }
-
- public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
- boolean visible) {
- return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
- }
-
- /* Writes all attributes from the original method. */
- public void visitAttribute(Attribute attr) {
- mParentVisitor.visitAttribute(attr);
- }
-
- /*
- * Only writes the first line number present in the original code so that source
- * viewers can direct to the correct method, even if the content doesn't match.
- */
- public void visitLineNumber(int line, Label start) {
- if (mOutputFirstLineNumber) {
- mParentVisitor.visitLineNumber(line, start);
- mOutputFirstLineNumber = false;
- }
- }
-
- public void visitInsn(int opcode) {
- // Skip original code.
- }
-
- public void visitLabel(Label label) {
- // Skip original code.
- }
-
- public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
- // Skip original code.
- }
-
- public void visitMethodInsn(int opcode, String owner, String name, String desc) {
- // Skip original code.
- }
-
- public void visitFieldInsn(int opcode, String owner, String name, String desc) {
- // Skip original code.
- }
-
- public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
- // Skip original code.
- }
-
- public void visitIincInsn(int var, int increment) {
- // Skip original code.
- }
-
- public void visitIntInsn(int opcode, int operand) {
- // Skip original code.
- }
-
- public void visitJumpInsn(int opcode, Label label) {
- // Skip original code.
- }
-
- public void visitLdcInsn(Object cst) {
- // Skip original code.
- }
-
- public void visitLocalVariable(String name, String desc, String signature,
- Label start, Label end, int index) {
- // Skip original code.
- }
-
- public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
- // Skip original code.
- }
-
- public void visitMultiANewArrayInsn(String desc, int dims) {
- // Skip original code.
- }
-
- public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
- // Skip original code.
- }
-
- public void visitTypeInsn(int opcode, String type) {
- // Skip original code.
- }
-
- public void visitVarInsn(int opcode, int var) {
- // Skip original code.
- }
-
-}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
new file mode 100644
index 0000000..ac4ae6d
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.ArrayList;
+
+/**
+ * This method adapter generates delegate methods.
+ * <p/>
+ * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
+ * <ul>
+ * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
+ * The content is the original method as-is from the reader.
+ * This step is omitted if the method is native, since it has no Java implementation.
+ * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a
+ * non-existing method named {@code SomeClass_Delegate.MethodName()}.
+ * The implementation of this 'delegate' method is done in layoutlib_brigde.
+ * </ul>
+ * A method visitor is generally constructed to generate a single method; however
+ * here we might want to generate one or two depending on the context. To achieve
+ * that, the visitor here generates the 'original' method and acts as a no-op if
+ * no such method exists (e.g. when the original is a native method).
+ * The delegate method is generated after the {@code visitEnd} of the original method
+ * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()}
+ * for native methods.
+ * <p/>
+ * When generating the 'delegate', the implementation generates a call to a class
+ * class named <code><className>_Delegate</code> with static methods matching
+ * the methods to be overridden here. The methods have the same return type.
+ * The argument type list is the same except the "this" reference is passed first
+ * for non-static methods.
+ * <p/>
+ * A new annotation is added to these 'delegate' methods so that we can easily find them
+ * for automated testing.
+ * <p/>
+ * This class isn't intended to be generic or reusable.
+ * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
+ * the two method writers for the original and the delegate class, as needed, with their
+ * expected names.
+ * <p/>
+ * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit.
+ * <p/>
+ * Instances of this class are not re-usable.
+ * The class adapter creates a new instance for each method.
+ */
+class DelegateMethodAdapter2 implements MethodVisitor {
+
+ /** Suffix added to delegate classes. */
+ public static final String DELEGATE_SUFFIX = "_Delegate";
+
+ /** The parent method writer to copy of the original method.
+ * Null when dealing with a native original method. */
+ private MethodVisitor mOrgWriter;
+ /** The parent method writer to generate the delegating method. Never null. */
+ private MethodVisitor mDelWriter;
+ /** The original method descriptor (return type + argument types.) */
+ private String mDesc;
+ /** True if the original method is static. */
+ private final boolean mIsStatic;
+ /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+ private final String mClassName;
+ /** The method name. */
+ private final String mMethodName;
+ /** Logger object. */
+ private final Log mLog;
+
+ /** Array used to capture the first line number information from the original method
+ * and duplicate it in the delegate. */
+ private Object[] mDelegateLineNumber;
+
+ /**
+ * Creates a new {@link DelegateMethodAdapter2} that will transform this method
+ * into a delegate call.
+ * <p/>
+ * See {@link DelegateMethodAdapter2} for more details.
+ *
+ * @param log The logger object. Must not be null.
+ * @param mvOriginal The parent method writer to copy of the original method.
+ * Must be {@code null} when dealing with a native original method.
+ * @param mvDelegate The parent method writer to generate the delegating method.
+ * Must never be null.
+ * @param className The internal class name of the class to visit,
+ * e.g. <code>com/android/SomeClass$InnerClass</code>.
+ * @param methodName The simple name of the method.
+ * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+ * {@link Type#getArgumentTypes(String)})
+ * @param isStatic True if the method is declared static.
+ */
+ public DelegateMethodAdapter2(Log log,
+ MethodVisitor mvOriginal,
+ MethodVisitor mvDelegate,
+ String className,
+ String methodName,
+ String desc,
+ boolean isStatic) {
+ mLog = log;
+ mOrgWriter = mvOriginal;
+ mDelWriter = mvDelegate;
+ mClassName = className;
+ mMethodName = methodName;
+ mDesc = desc;
+ mIsStatic = isStatic;
+ }
+
+ /**
+ * Generates the new code for the method.
+ * <p/>
+ * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+ * (since they have no code to visit).
+ * <p/>
+ * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+ * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
+ * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+ * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+ */
+ public void generateDelegateCode() {
+ /*
+ * The goal is to generate a call to a static delegate method.
+ * If this method is non-static, the first parameter will be 'this'.
+ * All the parameters must be passed and then the eventual return type returned.
+ *
+ * Example, let's say we have a method such as
+ * public void myMethod(int a, Object b, ArrayList<String> c) { ... }
+ *
+ * We'll want to create a body that calls a delegate method like this:
+ * TheClass_Delegate.myMethod(this, a, b, c);
+ *
+ * If the method is non-static and the class name is an inner class (e.g. has $ in its
+ * last segment), we want to push the 'this' of the outer class first:
+ * OuterClass_InnerClass_Delegate.myMethod(
+ * OuterClass.this,
+ * OuterClass$InnerClass.this,
+ * a, b, c);
+ *
+ * Only one level of inner class is supported right now, for simplicity and because
+ * we don't need more.
+ *
+ * The generated class name is the current class name with "_Delegate" appended to it.
+ * One thing to realize is that we don't care about generics -- since generic types
+ * are erased at build time, they have no influence on the method name being called.
+ */
+
+ // Add our annotation
+ AnnotationVisitor aw = mDelWriter.visitAnnotation(
+ Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+ true); // visible at runtime
+ if (aw != null) {
+ aw.visitEnd();
+ }
+
+ mDelWriter.visitCode();
+
+ if (mDelegateLineNumber != null) {
+ Object[] p = mDelegateLineNumber;
+ mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
+ }
+
+ ArrayList<Type> paramTypes = new ArrayList<Type>();
+ String delegateClassName = mClassName + DELEGATE_SUFFIX;
+ boolean pushedArg0 = false;
+ int maxStack = 0;
+
+ // For an instance method (e.g. non-static), push the 'this' preceded
+ // by the 'this' of any outer class, if any.
+ if (!mIsStatic) {
+ // Check if the last segment of the class name has inner an class.
+ // Right now we only support one level of inner classes.
+ int slash = mClassName.lastIndexOf('/');
+ int dol = mClassName.lastIndexOf('$');
+ if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
+ String outerClass = mClassName.substring(0, dol);
+ Type outerType = Type.getObjectType(outerClass);
+
+ // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
+ delegateClassName = delegateClassName.replace('$', '_');
+
+ // The first-level inner class has a package-protected member called 'this$0'
+ // that points to the outer class.
+
+ // Push this.getField("this$0") on the call stack.
+ mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
+ mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
+ mClassName, // class where the field is defined
+ "this$0", // field name
+ outerType.getDescriptor()); // type of the field
+ maxStack++;
+ paramTypes.add(outerType);
+ }
+
+ // Push "this" for the instance method, which is always ALOAD 0
+ mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
+ maxStack++;
+ pushedArg0 = true;
+ paramTypes.add(Type.getObjectType(mClassName));
+ }
+
+ // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
+ Type[] argTypes = Type.getArgumentTypes(mDesc);
+ int maxLocals = pushedArg0 ? 1 : 0;
+ for (Type t : argTypes) {
+ int size = t.getSize();
+ mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
+ maxLocals += size;
+ maxStack += size;
+ paramTypes.add(t);
+ }
+
+ // Construct the descriptor of the delegate based on the parameters
+ // we pushed on the call stack. The return type remains unchanged.
+ String desc = Type.getMethodDescriptor(
+ Type.getReturnType(mDesc),
+ paramTypes.toArray(new Type[paramTypes.size()]));
+
+ // Invoke the static delegate
+ mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
+ delegateClassName,
+ mMethodName,
+ desc);
+
+ Type returnType = Type.getReturnType(mDesc);
+ mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+ mDelWriter.visitMaxs(maxStack, maxLocals);
+ mDelWriter.visitEnd();
+
+ // For debugging now. Maybe we should collect these and store them in
+ // a text file for helping create the delegates. We could also compare
+ // the text file to a golden and break the build on unsupported changes
+ // or regressions. Even better we could fancy-print something that looks
+ // like the expected Java method declaration.
+ mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+ }
+
+ /* Pass down to visitor writer. In this implementation, either do nothing. */
+ public void visitCode() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitCode();
+ }
+ }
+
+ /*
+ * visitMaxs is called just before visitEnd if there was any code to rewrite.
+ */
+ public void visitMaxs(int maxStack, int maxLocals) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMaxs(maxStack, maxLocals);
+ }
+ }
+
+ /** End of visiting. Generate the delegating code. */
+ public void visitEnd() {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitEnd();
+ }
+ generateDelegateCode();
+ }
+
+ /* Writes all annotation from the original method. */
+ public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotation(desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all annotation default values from the original method. */
+ public AnnotationVisitor visitAnnotationDefault() {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitAnnotationDefault();
+ } else {
+ return null;
+ }
+ }
+
+ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+ boolean visible) {
+ if (mOrgWriter != null) {
+ return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
+ } else {
+ return null;
+ }
+ }
+
+ /* Writes all attributes from the original method. */
+ public void visitAttribute(Attribute attr) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitAttribute(attr);
+ }
+ }
+
+ /*
+ * Only writes the first line number present in the original code so that source
+ * viewers can direct to the correct method, even if the content doesn't match.
+ */
+ public void visitLineNumber(int line, Label start) {
+ // Capture the first line values for the new delegate method
+ if (mDelegateLineNumber == null) {
+ mDelegateLineNumber = new Object[] { line, start };
+ }
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLineNumber(line, start);
+ }
+ }
+
+ public void visitInsn(int opcode) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitInsn(opcode);
+ }
+ }
+
+ public void visitLabel(Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLabel(label);
+ }
+ }
+
+ public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTryCatchBlock(start, end, handler, type);
+ }
+ }
+
+ public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
+ }
+ }
+
+ public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
+ }
+ }
+
+ public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
+ }
+ }
+
+ public void visitIincInsn(int var, int increment) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIincInsn(var, increment);
+ }
+ }
+
+ public void visitIntInsn(int opcode, int operand) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitIntInsn(opcode, operand);
+ }
+ }
+
+ public void visitJumpInsn(int opcode, Label label) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitJumpInsn(opcode, label);
+ }
+ }
+
+ public void visitLdcInsn(Object cst) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLdcInsn(cst);
+ }
+ }
+
+ public void visitLocalVariable(String name, String desc, String signature,
+ Label start, Label end, int index) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
+ }
+ }
+
+ public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
+ }
+ }
+
+ public void visitMultiANewArrayInsn(String desc, int dims) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitMultiANewArrayInsn(desc, dims);
+ }
+ }
+
+ public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
+ }
+ }
+
+ public void visitTypeInsn(int opcode, String type) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitTypeInsn(opcode, type);
+ }
+ }
+
+ public void visitVarInsn(int opcode, int var) {
+ if (mOrgWriter != null) {
+ mOrgWriter.visitVarInsn(opcode, var);
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
index 9a57a4a..d70d028 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -31,7 +31,7 @@
private static String CONSTRUCTOR = "<init>";
private static String CLASS_INIT = "<clinit>";
-
+
/** The parent method writer */
private MethodVisitor mParentVisitor;
/** The method return type. Can be null. */
@@ -40,7 +40,7 @@
private String mInvokeSignature;
/** Flag to output the first line number. */
private boolean mOutputFirstLineNumber = true;
- /** Flag that is true when implementing a constructor, to accept all original
+ /** Flag that is true when implementing a constructor, to accept all original
* code calling the original super constructor. */
private boolean mIsInitMethod = false;
@@ -55,12 +55,12 @@
mInvokeSignature = invokeSignature;
mIsStatic = isStatic;
mIsNative = isNative;
-
+
if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
mIsInitMethod = true;
}
}
-
+
private void generateInvoke() {
/* Generates the code:
* OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
@@ -188,7 +188,7 @@
}
mParentVisitor.visitMaxs(maxStack, maxLocals);
}
-
+
/**
* End of visiting.
* For non-constructor, generate the messaging code and the return statement
@@ -250,6 +250,7 @@
generatePop();
generateInvoke();
mMessageGenerated = true;
+ //$FALL-THROUGH$
default:
mParentVisitor.visitInsn(opcode);
}
@@ -346,5 +347,5 @@
mParentVisitor.visitVarInsn(opcode, var);
}
}
-
+
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index e8b3ea8..6e120ce 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -130,7 +130,7 @@
}
/**
- * {@link DelegateMethodAdapter} does not support overriding constructors yet,
+ * {@link DelegateMethodAdapter2} does not support overriding constructors yet,
* so this should fail with an {@link UnsupportedOperationException}.
*
* Although not tested here, the message of the exception should contain the
@@ -202,6 +202,7 @@
// We'll delegate the "get" method of both the inner and outer class.
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("get");
+ delegateMethods.add("privateMethod");
// Generate the delegate for the outer class.
ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
@@ -234,6 +235,25 @@
// The original Outer.get returns 1+10+20,
// but the delegate makes it return 4+10+20
assertEquals(4+10+20, callGet(o2, 10, 20));
+ assertEquals(1+10+20, callGet_Original(o2, 10, 20));
+
+ // The original Outer has a private method that is
+ // delegated. We should be able to call both the delegate
+ // and the original (which is now public).
+ assertEquals("outerPrivateMethod",
+ callMethod(o2, "privateMethod_Original", false /*makePublic*/));
+
+ // The original method is private, so by default we can't access it
+ boolean gotIllegalAccessException = false;
+ try {
+ callMethod(o2, "privateMethod", false /*makePublic*/);
+ } catch(IllegalAccessException e) {
+ gotIllegalAccessException = true;
+ }
+ assertTrue(gotIllegalAccessException);
+ // Try again, but now making it accessible
+ assertEquals("outerPrivate_Delegate",
+ callMethod(o2, "privateMethod", true /*makePublic*/));
// Check the inner class. Since it's not a static inner class, we need
// to use the hidden constructor that takes the outer class as first parameter.
@@ -246,6 +266,7 @@
// The original Inner.get returns 3+10+20,
// but the delegate makes it return 6+10+20
assertEquals(6+10+20, callGet(i2, 10, 20));
+ assertEquals(3+10+20, callGet_Original(i2, 10, 20));
}
};
cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
@@ -319,7 +340,7 @@
}
/**
- * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection.
+ * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection.
*/
public int callGet(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get",
@@ -330,6 +351,39 @@
}
/**
+ * Accesses the "_Original" methods for {@link OuterClass#get}
+ * or {@link InnerClass#get}via reflection.
+ */
+ public int callGet_Original(Object instance, int a, long b) throws Exception {
+ Method m = instance.getClass().getMethod("get_Original",
+ new Class<?>[] { int.class, long.class } );
+
+ Object result = m.invoke(instance, new Object[] { a, b });
+ return ((Integer) result).intValue();
+ }
+
+ /**
+ * Accesses the any declared method that takes no parameter via reflection.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception {
+ Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null);
+
+ boolean wasAccessible = m.isAccessible();
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(true);
+ }
+
+ Object result = m.invoke(instance, (Object[])null);
+
+ if (makePublic && !wasAccessible) {
+ m.setAccessible(false);
+ }
+
+ return (T) result;
+ }
+
+ /**
* Accesses {@link ClassWithNative#add(int, int)} via reflection.
*/
public int callAdd(Object instance, int a, int b) throws Exception {
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index 9dc2f69..f083e76 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -39,10 +39,15 @@
public InnerClass() {
}
- // Inner.get returns 1+2=3 + a + b
+ // Inner.get returns 2 + 1 + a + b
public int get(int a, long b) {
return 2 + mOuterValue + a + (int) b;
}
}
+
+ @SuppressWarnings("unused")
+ private String privateMethod() {
+ return "outerPrivateMethod";
+ }
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
index 3252d87..774be8e 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
@@ -26,5 +26,9 @@
public static int get(OuterClass instance, int a, long b) {
return 4 + a + (int) b;
}
+
+ public static String privateMethod(OuterClass instance) {
+ return "outerPrivate_Delegate";
+ }
}
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 4ee86b6..018e6de 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -71,6 +71,7 @@
class SipHelper {
private static final String TAG = SipHelper.class.getSimpleName();
private static final boolean DEBUG = true;
+ private static final boolean DEBUG_PING = false;
private SipStack mSipStack;
private SipProvider mSipProvider;
@@ -177,17 +178,19 @@
return uri;
}
- public ClientTransaction sendKeepAlive(SipProfile userProfile, String tag)
- throws SipException {
+ public ClientTransaction sendOptions(SipProfile caller, SipProfile callee,
+ String tag) throws SipException {
try {
- Request request = createRequest(Request.OPTIONS, userProfile, tag);
+ Request request = (caller == callee)
+ ? createRequest(Request.OPTIONS, caller, tag)
+ : createRequest(Request.OPTIONS, caller, callee, tag);
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
clientTransaction.sendRequest();
return clientTransaction;
} catch (Exception e) {
- throw new SipException("sendKeepAlive()", e);
+ throw new SipException("sendOptions()", e);
}
}
@@ -249,23 +252,29 @@
return ct;
}
+ private Request createRequest(String requestType, SipProfile caller,
+ SipProfile callee, String tag) throws ParseException, SipException {
+ FromHeader fromHeader = createFromHeader(caller, tag);
+ ToHeader toHeader = createToHeader(callee);
+ SipURI requestURI = callee.getUri();
+ List<ViaHeader> viaHeaders = createViaHeaders();
+ CallIdHeader callIdHeader = createCallIdHeader();
+ CSeqHeader cSeqHeader = createCSeqHeader(requestType);
+ MaxForwardsHeader maxForwards = createMaxForwardsHeader();
+
+ Request request = mMessageFactory.createRequest(requestURI,
+ requestType, callIdHeader, cSeqHeader, fromHeader,
+ toHeader, viaHeaders, maxForwards);
+
+ request.addHeader(createContactHeader(caller));
+ return request;
+ }
+
public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
String sessionDescription, String tag)
throws SipException {
try {
- FromHeader fromHeader = createFromHeader(caller, tag);
- ToHeader toHeader = createToHeader(callee);
- SipURI requestURI = callee.getUri();
- List<ViaHeader> viaHeaders = createViaHeaders();
- CallIdHeader callIdHeader = createCallIdHeader();
- CSeqHeader cSeqHeader = createCSeqHeader(Request.INVITE);
- MaxForwardsHeader maxForwards = createMaxForwardsHeader();
-
- Request request = mMessageFactory.createRequest(requestURI,
- Request.INVITE, callIdHeader, cSeqHeader, fromHeader,
- toHeader, viaHeaders, maxForwards);
-
- request.addHeader(createContactHeader(caller));
+ Request request = createRequest(Request.INVITE, caller, callee, tag);
request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
"application", "sdp"));
@@ -419,9 +428,13 @@
public void sendResponse(RequestEvent event, int responseCode)
throws SipException {
try {
+ Request request = event.getRequest();
Response response = mMessageFactory.createResponse(
- responseCode, event.getRequest());
- if (DEBUG) Log.d(TAG, "send response: " + response);
+ responseCode, request);
+ if (DEBUG && (!Request.OPTIONS.equals(request.getMethod())
+ || DEBUG_PING)) {
+ Log.d(TAG, "send response: " + response);
+ }
getServerTransaction(event).sendResponse(response);
} catch (ParseException e) {
throw new SipException("sendResponse()", e);
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index 5ad5d26..3b0f5460 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -69,10 +69,11 @@
public final class SipService extends ISipService.Stub {
static final String TAG = "SipService";
static final boolean DEBUGV = false;
- static final boolean DEBUG = false;
+ static final boolean DEBUG = true;
private static final int EXPIRY_TIME = 3600;
private static final int SHORT_EXPIRY_TIME = 10;
private static final int MIN_EXPIRY_TIME = 60;
+ private static final int DEFAULT_KEEPALIVE_INTERVAL = 10; // in seconds
private Context mContext;
private String mLocalIp;
@@ -378,7 +379,7 @@
private void grabWifiLock() {
if (mWifiLock == null) {
- if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ acquire wifi lock");
+ if (DEBUG) Log.d(TAG, "acquire wifi lock");
mWifiLock = ((WifiManager)
mContext.getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
@@ -389,7 +390,7 @@
private void releaseWifiLock() {
if (mWifiLock != null) {
- if (DEBUG) Log.d(TAG, "~~~~~~~~~~~~~~~~~~~~~ release wifi lock");
+ if (DEBUG) Log.d(TAG, "release wifi lock");
mWifiLock.release();
mWifiLock = null;
stopWifiScanner();
@@ -459,9 +460,30 @@
}
}
- private void startPortMappingLifetimeMeasurement(SipSessionGroup group) {
- mIntervalMeasurementProcess = new IntervalMeasurementProcess(group);
- mIntervalMeasurementProcess.start();
+ private void startPortMappingLifetimeMeasurement(
+ SipProfile localProfile) {
+ startPortMappingLifetimeMeasurement(localProfile, -1);
+ }
+
+ private void startPortMappingLifetimeMeasurement(
+ SipProfile localProfile, int maxInterval) {
+ if ((mIntervalMeasurementProcess == null)
+ && (mKeepAliveInterval == -1)
+ && isBehindNAT(mLocalIp)) {
+ Log.d(TAG, "start NAT port mapping timeout measurement on "
+ + localProfile.getUriString());
+
+ mIntervalMeasurementProcess =
+ new IntervalMeasurementProcess(localProfile, maxInterval);
+ mIntervalMeasurementProcess.start();
+ }
+ }
+
+ private void restartPortMappingLifetimeMeasurement(
+ SipProfile localProfile, int maxInterval) {
+ stopPortMappingMeasurement();
+ mKeepAliveInterval = -1;
+ startPortMappingLifetimeMeasurement(localProfile, maxInterval);
}
private synchronized void addPendingSession(ISipSession session) {
@@ -500,6 +522,33 @@
return false;
}
+ private synchronized void onKeepAliveIntervalChanged() {
+ for (SipSessionGroupExt group : mSipGroups.values()) {
+ group.onKeepAliveIntervalChanged();
+ }
+ }
+
+ private int getKeepAliveInterval() {
+ return (mKeepAliveInterval < 0)
+ ? DEFAULT_KEEPALIVE_INTERVAL
+ : mKeepAliveInterval;
+ }
+
+ private boolean isBehindNAT(String address) {
+ try {
+ byte[] d = InetAddress.getByName(address).getAddress();
+ if ((d[0] == 10) ||
+ (((0x000000FF & ((int)d[0])) == 172) &&
+ ((0x000000F0 & ((int)d[1])) == 16)) ||
+ (((0x000000FF & ((int)d[0])) == 192) &&
+ ((0x000000FF & ((int)d[1])) == 168))) {
+ return true;
+ }
+ } catch (UnknownHostException e) {
+ Log.e(TAG, "isBehindAT()" + address, e);
+ }
+ return false;
+ }
private class SipSessionGroupExt extends SipSessionAdapter {
private SipSessionGroup mSipGroup;
@@ -527,6 +576,16 @@
return mSipGroup.containsSession(callId);
}
+ public void onKeepAliveIntervalChanged() {
+ mAutoRegistration.onKeepAliveIntervalChanged();
+ }
+
+ // TODO: remove this method once SipWakeupTimer can better handle variety
+ // of timeout values
+ void setWakeupTimer(SipWakeupTimer timer) {
+ mSipGroup.setWakeupTimer(timer);
+ }
+
// network connectivity is tricky because network can be disconnected
// at any instant so need to deal with exceptions carefully even when
// you think you are connected
@@ -534,7 +593,7 @@
SipProfile localProfile, String password) throws SipException {
try {
return new SipSessionGroup(localIp, localProfile, password,
- mMyWakeLock);
+ mTimer, mMyWakeLock);
} catch (IOException e) {
// network disconnected
Log.w(TAG, "createSipSessionGroup(): network disconnected?");
@@ -697,158 +756,135 @@
}
}
- private class IntervalMeasurementProcess extends SipSessionAdapter
- implements Runnable {
- private static final String TAG = "\\INTERVAL/";
- private static final int MAX_INTERVAL = 120; // seconds
- private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME;
- private static final int PASS_THRESHOLD = 6;
+ private class IntervalMeasurementProcess implements
+ SipSessionGroup.KeepAliveProcessCallback {
+ private static final String TAG = "SipKeepAliveInterval";
+ private static final int MAX_INTERVAL = 120; // in seconds
+ private static final int MIN_INTERVAL = 10; // in seconds
+ private static final int PASS_THRESHOLD = 10;
+ private static final int MAX_RETRY_COUNT = 5;
private SipSessionGroupExt mGroup;
private SipSessionGroup.SipSessionImpl mSession;
private boolean mRunning;
- private int mMinInterval = 10;
- private int mMaxInterval = MAX_INTERVAL;
- private int mInterval = MAX_INTERVAL / 2;
- private int mPassCounter = 0;
- private SipWakeupTimer mTimer = new SipWakeupTimer(mContext, mExecutor);
- // TODO: fix SipWakeupTimer so that we only use one instance of the timer
+ private int mMinInterval = 10; // in seconds
+ private int mMaxInterval;
+ private int mInterval;
+ private int mPassCount = 0;
+ private int mErrorCount = 0;
- public IntervalMeasurementProcess(SipSessionGroup group) {
+ public IntervalMeasurementProcess(SipProfile localProfile, int maxInterval) {
+ mMaxInterval = (maxInterval < 0) ? MAX_INTERVAL : maxInterval;
+ mInterval = (mMaxInterval + mMinInterval) / 2;
+
+ // Don't start measurement if the interval is too small
+ if (mInterval < MIN_INTERVAL) {
+ Log.w(TAG, "interval is too small; measurement aborted; "
+ + "maxInterval=" + mMaxInterval);
+ return;
+ }
+
try {
- mGroup = new SipSessionGroupExt(
- group.getLocalProfile(), null, null);
+ mGroup = new SipSessionGroupExt(localProfile, null, null);
+ // TODO: remove this line once SipWakeupTimer can better handle
+ // variety of timeout values
+ mGroup.setWakeupTimer(new SipWakeupTimer(mContext, mExecutor));
mSession = (SipSessionGroup.SipSessionImpl)
- mGroup.createSession(this);
+ mGroup.createSession(null);
} catch (Exception e) {
Log.w(TAG, "start interval measurement error: " + e);
}
}
public void start() {
- if (mRunning) return;
- mRunning = true;
- mTimer.set(mInterval * 1000, this);
- if (DEBUGV) Log.v(TAG, "start interval measurement");
- run();
+ synchronized (SipService.this) {
+ try {
+ mSession.startKeepAliveProcess(mInterval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "start()", e);
+ }
+ }
}
public void stop() {
- mRunning = false;
- mTimer.cancel(this);
+ synchronized (SipService.this) {
+ mSession.stopKeepAliveProcess();
+ }
}
private void restart() {
- mTimer.cancel(this);
- mTimer.set(mInterval * 1000, this);
- }
-
- private void calculateNewInterval() {
- if (!mSession.isReRegisterRequired()) {
- if (++mPassCounter != PASS_THRESHOLD) return;
- // update the interval, since the current interval is good to
- // keep the port mapping.
- mKeepAliveInterval = mMinInterval = mInterval;
- } else {
- // Since the rport is changed, shorten the interval.
- mSession.clearReRegisterRequired();
- mMaxInterval = mInterval;
- }
- if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) {
- // update mKeepAliveInterval and stop measurement.
- stop();
- mKeepAliveInterval = mMinInterval;
- if (DEBUGV) Log.v(TAG, "measured interval: " + mKeepAliveInterval);
- } else {
- // calculate the new interval and continue.
- mInterval = (mMaxInterval + mMinInterval) / 2;
- mPassCounter = 0;
- if (DEBUGV) {
- Log.v(TAG, " current interval: " + mKeepAliveInterval
- + "test new interval: " + mInterval);
- }
- restart();
- }
- }
-
- public void run() {
synchronized (SipService.this) {
- if (!mRunning) return;
try {
- mSession.sendKeepAlive();
- calculateNewInterval();
- } catch (Throwable t) {
+ mSession.stopKeepAliveProcess();
+ mSession.startKeepAliveProcess(mInterval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "restart()", e);
+ }
+ }
+ }
+
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onResponse(boolean portChanged) {
+ synchronized (SipService.this) {
+ mErrorCount = 0;
+
+ if (!portChanged) {
+ if (++mPassCount != PASS_THRESHOLD) return;
+ // update the interval, since the current interval is good to
+ // keep the port mapping.
+ mKeepAliveInterval = mMinInterval = mInterval;
+ if (DEBUG) {
+ Log.d(TAG, "measured good keepalive interval: "
+ + mKeepAliveInterval);
+ }
+ onKeepAliveIntervalChanged();
+ } else {
+ // Since the rport is changed, shorten the interval.
+ mMaxInterval = mInterval;
+ }
+ if ((mMaxInterval - mMinInterval) < MIN_INTERVAL) {
+ // update mKeepAliveInterval and stop measurement.
stop();
- Log.w(TAG, "interval measurement error: " + t);
+ mKeepAliveInterval = mMinInterval;
+ if (DEBUG) {
+ Log.d(TAG, "measured keepalive interval: "
+ + mKeepAliveInterval);
+ }
+ } else {
+ // calculate the new interval and continue.
+ mInterval = (mMaxInterval + mMinInterval) / 2;
+ mPassCount = 0;
+ if (DEBUG) {
+ Log.d(TAG, "current interval: " + mKeepAliveInterval
+ + ", test new interval: " + mInterval);
+ }
+ restart();
}
}
}
- }
- // KeepAliveProcess is controlled by AutoRegistrationProcess.
- // All methods will be invoked in sync with SipService.this.
- private class KeepAliveProcess implements Runnable {
- private static final String TAG = "\\KEEPALIVE/";
- private static final int INTERVAL = 10;
- private SipSessionGroup.SipSessionImpl mSession;
- private boolean mRunning = false;
- private int mInterval = INTERVAL;
-
- public KeepAliveProcess(SipSessionGroup.SipSessionImpl session) {
- mSession = session;
- }
-
- public void start() {
- if (mRunning) return;
- mRunning = true;
- mTimer.set(INTERVAL * 1000, this);
- }
-
- private void restart(int duration) {
- if (DEBUG) Log.d(TAG, "Refresh NAT port mapping " + duration + "s later.");
- mTimer.cancel(this);
- mTimer.set(duration * 1000, this);
- }
-
- // timeout handler
- public void run() {
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onError(int errorCode, String description) {
synchronized (SipService.this) {
- if (!mRunning) return;
-
- if (DEBUGV) Log.v(TAG, "~~~ keepalive: "
- + mSession.getLocalProfile().getUriString());
- SipSessionGroup.SipSessionImpl session = mSession.duplicate();
- try {
- session.sendKeepAlive();
- if (session.isReRegisterRequired()) {
- // Acquire wake lock for the registration process. The
- // lock will be released when registration is complete.
- mMyWakeLock.acquire(mSession);
- mSession.register(EXPIRY_TIME);
- }
- if (mKeepAliveInterval > mInterval) {
- mInterval = mKeepAliveInterval;
- restart(mInterval);
- }
- } catch (Throwable t) {
- Log.w(TAG, "keepalive error: " + t);
+ Log.w(TAG, "interval measurement error: " + description);
+ if (++mErrorCount < MAX_RETRY_COUNT) {
+ Log.w(TAG, " retry count = " + mErrorCount);
+ mPassCount = 0;
+ restart();
+ } else {
+ Log.w(TAG, " max retry count reached; measurement aborted");
}
}
}
-
- public void stop() {
- if (DEBUGV && (mSession != null)) Log.v(TAG, "stop keepalive:"
- + mSession.getLocalProfile().getUriString());
- mRunning = false;
- mSession = null;
- mTimer.cancel(this);
- }
}
private class AutoRegistrationProcess extends SipSessionAdapter
- implements Runnable {
+ implements Runnable, SipSessionGroup.KeepAliveProcessCallback {
+ private String TAG = "SipAudoReg";
private SipSessionGroup.SipSessionImpl mSession;
+ private SipSessionGroup.SipSessionImpl mKeepAliveSession;
private SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
- private KeepAliveProcess mKeepAliveProcess;
private int mBackoff = 1;
private boolean mRegistered;
private long mExpiryTime;
@@ -869,27 +905,51 @@
// return right away if no active network connection.
if (mSession == null) return;
- synchronized (SipService.this) {
- if (isBehindNAT(mLocalIp)
- && (mIntervalMeasurementProcess == null)
- && (mKeepAliveInterval == -1)) {
- // Start keep-alive interval measurement, here we allow
- // the first profile only as the target service provider
- // to measure the life time of NAT port mapping.
- startPortMappingLifetimeMeasurement(group);
- }
- }
-
// start unregistration to clear up old registration at server
// TODO: when rfc5626 is deployed, use reg-id and sip.instance
// in registration to avoid adding duplicate entries to server
mMyWakeLock.acquire(mSession);
mSession.unregister();
- if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess for "
- + mSession.getLocalProfile().getUriString());
+ if (DEBUG) TAG = mSession.getLocalProfile().getUriString();
+ if (DEBUG) Log.d(TAG, "start AutoRegistrationProcess");
}
}
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onResponse(boolean portChanged) {
+ synchronized (SipService.this) {
+ if (portChanged) {
+ restartPortMappingLifetimeMeasurement(
+ mSession.getLocalProfile(), getKeepAliveInterval());
+ } else {
+ // Start keep-alive interval measurement on the first
+ // successfully kept-alive SipSessionGroup
+ startPortMappingLifetimeMeasurement(
+ mSession.getLocalProfile());
+ }
+
+ if (!mRunning || !portChanged) return;
+
+ // The keep alive process is stopped when port is changed;
+ // Nullify the session so that the process can be restarted
+ // again when the re-registration is done
+ mKeepAliveSession = null;
+
+ // Acquire wake lock for the registration process. The
+ // lock will be released when registration is complete.
+ mMyWakeLock.acquire(mSession);
+ mSession.register(EXPIRY_TIME);
+ }
+ }
+
+ // SipSessionGroup.KeepAliveProcessCallback
+ @Override
+ public void onError(int errorCode, String description) {
+ Log.e(TAG, "keepalive error: " + description);
+ onResponse(true); // re-register immediately
+ }
+
public void stop() {
if (!mRunning) return;
mRunning = false;
@@ -900,15 +960,30 @@
}
mTimer.cancel(this);
- if (mKeepAliveProcess != null) {
- mKeepAliveProcess.stop();
- mKeepAliveProcess = null;
+ if (mKeepAliveSession != null) {
+ mKeepAliveSession.stopKeepAliveProcess();
+ mKeepAliveSession = null;
}
mRegistered = false;
setListener(mProxy.getListener());
}
+ public void onKeepAliveIntervalChanged() {
+ if (mKeepAliveSession != null) {
+ int newInterval = getKeepAliveInterval();
+ if (DEBUGV) {
+ Log.v(TAG, "restart keepalive w interval=" + newInterval);
+ }
+ mKeepAliveSession.stopKeepAliveProcess();
+ try {
+ mKeepAliveSession.startKeepAliveProcess(newInterval, this);
+ } catch (SipException e) {
+ Log.e(TAG, "onKeepAliveIntervalChanged()", e);
+ }
+ }
+ }
+
public void setListener(ISipSessionListener listener) {
synchronized (SipService.this) {
mProxy.setListener(listener);
@@ -955,13 +1030,14 @@
}
// timeout handler: re-register
+ @Override
public void run() {
synchronized (SipService.this) {
if (!mRunning) return;
mErrorCode = SipErrorCode.NO_ERROR;
mErrorMessage = null;
- if (DEBUG) Log.d(TAG, "~~~ registering");
+ if (DEBUG) Log.d(TAG, "registering");
if (mConnected) {
mMyWakeLock.acquire(mSession);
mSession.register(EXPIRY_TIME);
@@ -969,22 +1045,6 @@
}
}
- private boolean isBehindNAT(String address) {
- try {
- byte[] d = InetAddress.getByName(address).getAddress();
- if ((d[0] == 10) ||
- (((0x000000FF & ((int)d[0])) == 172) &&
- ((0x000000F0 & ((int)d[1])) == 16)) ||
- (((0x000000FF & ((int)d[0])) == 192) &&
- ((0x000000FF & ((int)d[1])) == 168))) {
- return true;
- }
- } catch (UnknownHostException e) {
- Log.e(TAG, "isBehindAT()" + address, e);
- }
- return false;
- }
-
private void restart(int duration) {
if (DEBUG) Log.d(TAG, "Refresh registration " + duration + "s later.");
mTimer.cancel(this);
@@ -1030,7 +1090,6 @@
mProxy.onRegistrationDone(session, duration);
if (duration > 0) {
- mSession.clearReRegisterRequired();
mExpiryTime = SystemClock.elapsedRealtime()
+ (duration * 1000);
@@ -1043,13 +1102,17 @@
}
restart(duration);
- if (isBehindNAT(mLocalIp) ||
- mSession.getLocalProfile().getSendKeepAlive()) {
- if (mKeepAliveProcess == null) {
- mKeepAliveProcess =
- new KeepAliveProcess(mSession);
+ SipProfile localProfile = mSession.getLocalProfile();
+ if ((mKeepAliveSession == null) && (isBehindNAT(mLocalIp)
+ || localProfile.getSendKeepAlive())) {
+ mKeepAliveSession = mSession.duplicate();
+ Log.d(TAG, "start keepalive");
+ try {
+ mKeepAliveSession.startKeepAliveProcess(
+ getKeepAliveInterval(), this);
+ } catch (SipException e) {
+ Log.e(TAG, "AutoRegistrationProcess", e);
}
- mKeepAliveProcess.start();
}
}
mMyWakeLock.release(session);
@@ -1103,10 +1166,6 @@
private void restartLater() {
mRegistered = false;
restart(backoffDuration());
- if (mKeepAliveProcess != null) {
- mKeepAliveProcess.stop();
- mKeepAliveProcess = null;
- }
}
}
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 4837eb9..2d0dd9c 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -28,6 +28,7 @@
import android.net.sip.SipErrorCode;
import android.net.sip.SipProfile;
import android.net.sip.SipSession;
+import android.net.sip.SipSessionAdapter;
import android.text.TextUtils;
import android.util.Log;
@@ -89,6 +90,7 @@
private static final String THREAD_POOL_SIZE = "1";
private static final int EXPIRY_TIME = 3600; // in seconds
private static final int CANCEL_CALL_TIMER = 3; // in seconds
+ private static final int KEEPALIVE_TIMEOUT = 3; // in seconds
private static final long WAKE_LOCK_HOLDING_TIME = 500; // in milliseconds
private static final EventObject DEREGISTER = new EventObject("Deregister");
@@ -107,6 +109,7 @@
private SipSessionImpl mCallReceiverSession;
private String mLocalIp;
+ private SipWakeupTimer mWakeupTimer;
private SipWakeLock mWakeLock;
// call-id-to-SipSession map
@@ -119,13 +122,21 @@
* @throws IOException if cannot assign requested address
*/
public SipSessionGroup(String localIp, SipProfile myself, String password,
- SipWakeLock wakeLock) throws SipException, IOException {
+ SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException,
+ IOException {
mLocalProfile = myself;
mPassword = password;
+ mWakeupTimer = timer;
mWakeLock = wakeLock;
reset(localIp);
}
+ // TODO: remove this method once SipWakeupTimer can better handle variety
+ // of timeout values
+ void setWakeupTimer(SipWakeupTimer timer) {
+ mWakeupTimer = timer;
+ }
+
synchronized void reset(String localIp) throws SipException, IOException {
mLocalIp = localIp;
if (localIp == null) return;
@@ -382,6 +393,12 @@
}
}
+ static interface KeepAliveProcessCallback {
+ /** Invoked when the response of keeping alive comes back. */
+ void onResponse(boolean portChanged);
+ void onError(int errorCode, String description);
+ }
+
class SipSessionImpl extends ISipSession.Stub {
SipProfile mPeerProfile;
SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
@@ -392,12 +409,10 @@
ClientTransaction mClientTransaction;
String mPeerSessionDescription;
boolean mInCall;
- SessionTimer mTimer;
+ SessionTimer mSessionTimer;
int mAuthenticationRetryCount;
- // for registration
- boolean mReRegisterFlag = false;
- int mRPort = 0;
+ private KeepAliveProcess mKeepAliveProcess;
// lightweight timer
class SessionTimer {
@@ -512,7 +527,9 @@
try {
processCommand(command);
} catch (Throwable e) {
- Log.w(TAG, "command error: " + command, e);
+ Log.w(TAG, "command error: " + command + ": "
+ + mLocalProfile.getUriString(),
+ getRootCause(e));
onError(e);
}
}
@@ -553,34 +570,6 @@
doCommandAsync(DEREGISTER);
}
- public boolean isReRegisterRequired() {
- return mReRegisterFlag;
- }
-
- public void clearReRegisterRequired() {
- mReRegisterFlag = false;
- }
-
- public void sendKeepAlive() {
- mState = SipSession.State.PINGING;
- try {
- processCommand(new OptionsCommand());
- for (int i = 0; i < 15; i++) {
- if (SipSession.State.PINGING != mState) break;
- Thread.sleep(200);
- }
- if (SipSession.State.PINGING == mState) {
- // FIXME: what to do if server doesn't respond
- reset();
- if (DEBUG) Log.w(TAG, "no response from ping");
- }
- } catch (SipException e) {
- Log.e(TAG, "sendKeepAlive failed", e);
- } catch (InterruptedException e) {
- Log.e(TAG, "sendKeepAlive interrupted", e);
- }
- }
-
private void processCommand(EventObject command) throws SipException {
if (isLoggable(command)) Log.d(TAG, "process cmd: " + command);
if (!process(command)) {
@@ -612,6 +601,11 @@
synchronized (SipSessionGroup.this) {
if (isClosed()) return false;
+ if (mKeepAliveProcess != null) {
+ // event consumed by keepalive process
+ if (mKeepAliveProcess.process(evt)) return true;
+ }
+
Dialog dialog = null;
if (evt instanceof RequestEvent) {
dialog = ((RequestEvent) evt).getDialog();
@@ -627,9 +621,6 @@
case SipSession.State.DEREGISTERING:
processed = registeringToReady(evt);
break;
- case SipSession.State.PINGING:
- processed = keepAliveProcess(evt);
- break;
case SipSession.State.READY_TO_CALL:
processed = readyForCall(evt);
break;
@@ -754,10 +745,6 @@
case SipSession.State.OUTGOING_CALL_CANCELING:
onError(SipErrorCode.TIME_OUT, event.toString());
break;
- case SipSession.State.PINGING:
- reset();
- mReRegisterFlag = true;
- break;
default:
Log.d(TAG, " do nothing");
@@ -778,48 +765,6 @@
return expires;
}
- private boolean keepAliveProcess(EventObject evt) throws SipException {
- if (evt instanceof OptionsCommand) {
- mClientTransaction = mSipHelper.sendKeepAlive(mLocalProfile,
- generateTag());
- mDialog = mClientTransaction.getDialog();
- addSipSession(this);
- return true;
- } else if (evt instanceof ResponseEvent) {
- return parseOptionsResult(evt);
- }
- return false;
- }
-
- private boolean parseOptionsResult(EventObject evt) {
- if (expectResponse(Request.OPTIONS, evt)) {
- ResponseEvent event = (ResponseEvent) evt;
- int rPort = getRPortFromResponse(event.getResponse());
- if (rPort != -1) {
- if (mRPort == 0) mRPort = rPort;
- if (mRPort != rPort) {
- mReRegisterFlag = true;
- if (DEBUG) Log.w(TAG, String.format(
- "rport is changed: %d <> %d", mRPort, rPort));
- mRPort = rPort;
- } else {
- if (DEBUG_PING) Log.w(TAG, "rport is the same: " + rPort);
- }
- } else {
- if (DEBUG) Log.w(TAG, "peer did not respond rport");
- }
- reset();
- return true;
- }
- return false;
- }
-
- private int getRPortFromResponse(Response response) {
- ViaHeader viaHeader = (ViaHeader)(response.getHeader(
- SIPHeaderNames.VIA));
- return (viaHeader == null) ? -1 : viaHeader.getRPort();
- }
-
private boolean registeringToReady(EventObject evt)
throws SipException {
if (expectResponse(Request.REGISTER, evt)) {
@@ -1138,15 +1083,15 @@
// timeout in seconds
private void startSessionTimer(int timeout) {
if (timeout > 0) {
- mTimer = new SessionTimer();
- mTimer.start(timeout);
+ mSessionTimer = new SessionTimer();
+ mSessionTimer.start(timeout);
}
}
private void cancelSessionTimer() {
- if (mTimer != null) {
- mTimer.cancel();
- mTimer = null;
+ if (mSessionTimer != null) {
+ mSessionTimer.cancel();
+ mSessionTimer = null;
}
}
@@ -1272,6 +1217,170 @@
onRegistrationFailed(getErrorCode(statusCode),
createErrorMessage(response));
}
+
+ // Notes: SipSessionListener will be replaced by the keepalive process
+ // @param interval in seconds
+ public void startKeepAliveProcess(int interval,
+ KeepAliveProcessCallback callback) throws SipException {
+ synchronized (SipSessionGroup.this) {
+ startKeepAliveProcess(interval, mLocalProfile, callback);
+ }
+ }
+
+ // Notes: SipSessionListener will be replaced by the keepalive process
+ // @param interval in seconds
+ public void startKeepAliveProcess(int interval, SipProfile peerProfile,
+ KeepAliveProcessCallback callback) throws SipException {
+ synchronized (SipSessionGroup.this) {
+ if (mKeepAliveProcess != null) {
+ throw new SipException("Cannot create more than one "
+ + "keepalive process in a SipSession");
+ }
+ mPeerProfile = peerProfile;
+ mKeepAliveProcess = new KeepAliveProcess();
+ mProxy.setListener(mKeepAliveProcess);
+ mKeepAliveProcess.start(interval, callback);
+ }
+ }
+
+ public void stopKeepAliveProcess() {
+ synchronized (SipSessionGroup.this) {
+ if (mKeepAliveProcess != null) {
+ mKeepAliveProcess.stop();
+ mKeepAliveProcess = null;
+ }
+ }
+ }
+
+ class KeepAliveProcess extends SipSessionAdapter implements Runnable {
+ private static final String TAG = "SipKeepAlive";
+ private boolean mRunning = false;
+ private KeepAliveProcessCallback mCallback;
+
+ private boolean mPortChanged = false;
+ private int mRPort = 0;
+ private int mInterval; // just for debugging
+
+ // @param interval in seconds
+ void start(int interval, KeepAliveProcessCallback callback) {
+ if (mRunning) return;
+ mRunning = true;
+ mInterval = interval;
+ mCallback = new KeepAliveProcessCallbackProxy(callback);
+ mWakeupTimer.set(interval * 1000, this);
+ if (DEBUG) {
+ Log.d(TAG, "start keepalive:"
+ + mLocalProfile.getUriString());
+ }
+
+ // No need to run the first time in a separate thread for now
+ run();
+ }
+
+ // return true if the event is consumed
+ boolean process(EventObject evt) throws SipException {
+ if (mRunning && (mState == SipSession.State.PINGING)) {
+ if (evt instanceof ResponseEvent) {
+ if (parseOptionsResult(evt)) {
+ if (mPortChanged) {
+ stop();
+ } else {
+ cancelSessionTimer();
+ removeSipSession(SipSessionImpl.this);
+ }
+ mCallback.onResponse(mPortChanged);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // SipSessionAdapter
+ // To react to the session timeout event and network error.
+ @Override
+ public void onError(ISipSession session, int errorCode, String message) {
+ stop();
+ mCallback.onError(errorCode, message);
+ }
+
+ // SipWakeupTimer timeout handler
+ // To send out keepalive message.
+ @Override
+ public void run() {
+ synchronized (SipSessionGroup.this) {
+ if (!mRunning) return;
+
+ if (DEBUG_PING) {
+ Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
+ + " --> " + mPeerProfile + ", interval=" + mInterval);
+ }
+ try {
+ sendKeepAlive();
+ } catch (Throwable t) {
+ Log.w(TAG, "keepalive error: " + ": "
+ + mLocalProfile.getUriString(), getRootCause(t));
+ // It's possible that the keepalive process is being stopped
+ // during session.sendKeepAlive() so need to check mRunning
+ // again here.
+ if (mRunning) SipSessionImpl.this.onError(t);
+ }
+ }
+ }
+
+ void stop() {
+ synchronized (SipSessionGroup.this) {
+ if (DEBUG) {
+ Log.d(TAG, "stop keepalive:" + mLocalProfile.getUriString()
+ + ",RPort=" + mRPort);
+ }
+ mRunning = false;
+ mWakeupTimer.cancel(this);
+ reset();
+ }
+ }
+
+ private void sendKeepAlive() throws SipException, InterruptedException {
+ synchronized (SipSessionGroup.this) {
+ mState = SipSession.State.PINGING;
+ mClientTransaction = mSipHelper.sendOptions(
+ mLocalProfile, mPeerProfile, generateTag());
+ mDialog = mClientTransaction.getDialog();
+ addSipSession(SipSessionImpl.this);
+
+ startSessionTimer(KEEPALIVE_TIMEOUT);
+ // when timed out, onError() will be called with SipErrorCode.TIME_OUT
+ }
+ }
+
+ private boolean parseOptionsResult(EventObject evt) {
+ if (expectResponse(Request.OPTIONS, evt)) {
+ ResponseEvent event = (ResponseEvent) evt;
+ int rPort = getRPortFromResponse(event.getResponse());
+ if (rPort != -1) {
+ if (mRPort == 0) mRPort = rPort;
+ if (mRPort != rPort) {
+ mPortChanged = true;
+ if (DEBUG) Log.d(TAG, String.format(
+ "rport is changed: %d <> %d", mRPort, rPort));
+ mRPort = rPort;
+ } else {
+ if (DEBUG) Log.d(TAG, "rport is the same: " + rPort);
+ }
+ } else {
+ if (DEBUG) Log.w(TAG, "peer did not respond rport");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private int getRPortFromResponse(Response response) {
+ ViaHeader viaHeader = (ViaHeader)(response.getHeader(
+ SIPHeaderNames.VIA));
+ return (viaHeader == null) ? -1 : viaHeader.getRPort();
+ }
+ }
}
/**
@@ -1363,15 +1472,16 @@
if (!isLoggable(s)) return false;
if (evt == null) return false;
- if (evt instanceof OptionsCommand) {
- return DEBUG_PING;
- } else if (evt instanceof ResponseEvent) {
+ if (evt instanceof ResponseEvent) {
Response response = ((ResponseEvent) evt).getResponse();
if (Request.OPTIONS.equals(response.getHeader(CSeqHeader.NAME))) {
return DEBUG_PING;
}
return DEBUG;
} else if (evt instanceof RequestEvent) {
+ if (isRequestEvent(Request.OPTIONS, evt)) {
+ return DEBUG_PING;
+ }
return DEBUG;
}
return false;
@@ -1387,12 +1497,6 @@
}
}
- private class OptionsCommand extends EventObject {
- public OptionsCommand() {
- super(SipSessionGroup.this);
- }
- }
-
private class RegisterCommand extends EventObject {
private int mDuration;
@@ -1434,4 +1538,46 @@
return mTimeout;
}
}
+
+ /** Class to help safely run KeepAliveProcessCallback in a different thread. */
+ static class KeepAliveProcessCallbackProxy implements KeepAliveProcessCallback {
+ private KeepAliveProcessCallback mCallback;
+
+ KeepAliveProcessCallbackProxy(KeepAliveProcessCallback callback) {
+ mCallback = callback;
+ }
+
+ private void proxy(Runnable runnable) {
+ // One thread for each calling back.
+ // Note: Guarantee ordering if the issue becomes important. Currently,
+ // the chance of handling two callback events at a time is none.
+ new Thread(runnable, "SIP-KeepAliveProcessCallbackThread").start();
+ }
+
+ public void onResponse(final boolean portChanged) {
+ if (mCallback == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mCallback.onResponse(portChanged);
+ } catch (Throwable t) {
+ Log.w(TAG, "onResponse", t);
+ }
+ }
+ });
+ }
+
+ public void onError(final int errorCode, final String description) {
+ if (mCallback == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mCallback.onError(errorCode, description);
+ } catch (Throwable t) {
+ Log.w(TAG, "onError", t);
+ }
+ }
+ });
+ }
+ }
}
diff --git a/voip/java/com/android/server/sip/SipWakeupTimer.java b/voip/java/com/android/server/sip/SipWakeupTimer.java
index 9cc26b0..76780c0 100644
--- a/voip/java/com/android/server/sip/SipWakeupTimer.java
+++ b/voip/java/com/android/server/sip/SipWakeupTimer.java
@@ -173,7 +173,7 @@
long triggerTime = event.mTriggerTime;
if (DEBUG_TIMER) {
- Log.d(TAG, " add event " + event + " scheduled at "
+ Log.d(TAG, " add event " + event + " scheduled on "
+ showTime(triggerTime) + " at " + showTime(now)
+ ", #events=" + mEventQueue.size());
printQueue();
@@ -267,10 +267,10 @@
if (stopped() || mEventQueue.isEmpty()) return;
for (MyEvent event : mEventQueue) {
- if (event.mTriggerTime != triggerTime) break;
+ if (event.mTriggerTime != triggerTime) continue;
if (DEBUG_TIMER) Log.d(TAG, "execute " + event);
- event.mLastTriggerTime = event.mTriggerTime;
+ event.mLastTriggerTime = triggerTime;
event.mTriggerTime += event.mPeriod;
// run the callback in the handler thread to prevent deadlock
@@ -324,6 +324,8 @@
}
}
+ // Sort the events by mMaxPeriod so that the first event can be used to
+ // align events with larger periods
private static class MyEventComparator implements Comparator<MyEvent> {
public int compare(MyEvent e1, MyEvent e2) {
if (e1 == e2) return 0;