Merge "Turn on HW accel by default for apps that target ICS."
diff --git a/Android.mk b/Android.mk
index f752f44..5f3327c 100644
--- a/Android.mk
+++ b/Android.mk
@@ -90,6 +90,7 @@
core/java/android/bluetooth/IBluetoothA2dp.aidl \
core/java/android/bluetooth/IBluetoothCallback.aidl \
core/java/android/bluetooth/IBluetoothHeadset.aidl \
+ core/java/android/bluetooth/IBluetoothHealthCallback.aidl \
core/java/android/bluetooth/IBluetoothPbap.aidl \
core/java/android/content/IClipboard.aidl \
core/java/android/content/IContentService.aidl \
@@ -244,6 +245,7 @@
frameworks/base/core/java/android/app/Notification.aidl \
frameworks/base/core/java/android/app/PendingIntent.aidl \
frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \
+ frameworks/base/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl \
frameworks/base/core/java/android/content/ComponentName.aidl \
frameworks/base/core/java/android/content/Intent.aidl \
frameworks/base/core/java/android/content/IntentSender.aidl \
diff --git a/api/current.txt b/api/current.txt
index b918149..0cb32f0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -601,8 +601,10 @@
field public static final int layout_height = 16842997; // 0x10100f5
field public static final int layout_margin = 16842998; // 0x10100f6
field public static final int layout_marginBottom = 16843002; // 0x10100fa
+ field public static final int layout_marginEnd = 16843675; // 0x101039b
field public static final int layout_marginLeft = 16842999; // 0x10100f7
field public static final int layout_marginRight = 16843001; // 0x10100f9
+ field public static final int layout_marginStart = 16843674; // 0x101039a
field public static final int layout_marginTop = 16843000; // 0x10100f8
field public static final int layout_row = 16843643; // 0x101037b
field public static final int layout_rowSpan = 16843644; // 0x101037c
@@ -8364,6 +8366,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 +10436,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);
@@ -17481,7 +17494,7 @@
public final class KeyChain {
ctor public KeyChain();
- method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int);
+ method public static void choosePrivateKeyAlias(android.app.Activity, android.security.KeyChainAliasCallback, java.lang.String[], java.security.Principal[], java.lang.String, int, java.lang.String);
method public static java.security.cert.X509Certificate[] getCertificateChain(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException;
method public static java.security.PrivateKey getPrivateKey(android.content.Context, java.lang.String) throws java.lang.InterruptedException, android.security.KeyChainException;
}
@@ -22044,6 +22057,7 @@
method protected boolean verifyDrawable(android.graphics.drawable.Drawable);
method public boolean willNotCacheDrawing();
method public boolean willNotDraw();
+ field public static android.util.Property ALPHA;
field public static final int DRAWING_CACHE_QUALITY_AUTO = 0; // 0x0
field public static final int DRAWING_CACHE_QUALITY_HIGH = 1048576; // 0x100000
field public static final int DRAWING_CACHE_QUALITY_LOW = 524288; // 0x80000
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 424b70a..479b70a 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -33,6 +33,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.util.AndroidException;
import android.view.IWindowManager;
@@ -199,6 +200,10 @@
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else if (opt.equals("--grant-write-uri-permission")) {
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } else if (opt.equals("--exclude-stopped-packages")) {
+ intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+ } else if (opt.equals("--include-stopped-packages")) {
+ intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
} else if (opt.equals("--debug-log-resolution")) {
intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
} else if (opt.equals("--activity-brought-to-front")) {
@@ -227,6 +232,10 @@
intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
} else if (opt.equals("--activity-single-top")) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ } else if (opt.equals("--activity-clear-task")) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ } else if (opt.equals("--activity-task-on-home")) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
} else if (opt.equals("--receiver-registered-only")) {
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
} else if (opt.equals("--receiver-replace-pending")) {
@@ -400,7 +409,8 @@
argKey = nextArgRequired();
argValue = nextArgRequired();
args.putString(argKey, argValue);
- } else if (opt.equals("--no_window_animation")) {
+ } else if (opt.equals("--no_window_animation")
+ || opt.equals("--no-window-animation")) {
no_window_animation = true;
} else {
System.err.println("Error: Unknown option: " + opt);
@@ -440,15 +450,43 @@
}
}
+ static void removeWallOption() {
+ String props = SystemProperties.get("dalvik.vm.extra-opts");
+ if (props != null && props.contains("-Xprofile:wallclock")) {
+ props = props.replace("-Xprofile:wallclock", "");
+ props = props.trim();
+ SystemProperties.set("dalvik.vm.extra-opts", props);
+ }
+ }
+
private void runProfile() throws Exception {
String profileFile = null;
boolean start = false;
- String process = nextArgRequired();
- ParcelFileDescriptor fd = null;
-
+ boolean wall = false;
+
+ String process = null;
+
String cmd = nextArgRequired();
if ("start".equals(cmd)) {
start = true;
+ wall = "--wall".equals(nextOption());
+ process = nextArgRequired();
+ } else if ("stop".equals(cmd)) {
+ process = nextArgRequired();
+ } else {
+ // Compatibility with old syntax: process is specified first.
+ process = cmd;
+ cmd = nextArgRequired();
+ if ("start".equals(cmd)) {
+ start = true;
+ } else if (!"stop".equals(cmd)) {
+ throw new IllegalArgumentException("Profile command " + process + " not valid");
+ }
+ }
+
+ ParcelFileDescriptor fd = null;
+
+ if (start) {
profileFile = nextArgRequired();
try {
fd = ParcelFileDescriptor.open(
@@ -460,12 +498,27 @@
System.err.println("Error: Unable to open file: " + profileFile);
return;
}
- } else if (!"stop".equals(cmd)) {
- throw new IllegalArgumentException("Profile command " + cmd + " not valid");
}
- if (!mAm.profileControl(process, start, profileFile, fd)) {
- throw new AndroidException("PROFILE FAILED on process " + process);
+ try {
+ if (wall) {
+ // XXX doesn't work -- this needs to be set before booting.
+ String props = SystemProperties.get("dalvik.vm.extra-opts");
+ if (props == null || !props.contains("-Xprofile:wallclock")) {
+ props = props + " -Xprofile:wallclock";
+ //SystemProperties.set("dalvik.vm.extra-opts", props);
+ }
+ } else if (start) {
+ //removeWallOption();
+ }
+ if (!mAm.profileControl(process, start, profileFile, fd)) {
+ wall = false;
+ throw new AndroidException("PROFILE FAILED on process " + process);
+ }
+ } finally {
+ if (!wall) {
+ //removeWallOption();
+ }
}
}
@@ -1012,62 +1065,76 @@
private static void showUsage() {
System.err.println(
"usage: am [subcommand] [options]\n" +
+ "usage: am start [-D] [-W] <INTENT>\n" +
+ " am startservice <INTENT>\n" +
+ " am force-stop <PACKAGE>\n" +
+ " am broadcast <INTENT>\n" +
+ " am instrument [-r] [-e <NAME> <VALUE>] [-p] [-w]\n" +
+ " [--no-window-animation] <COMPONENT>\n" +
+ " am profile start <PROCESS> <FILE>\n" +
+ " am profile stop <PROCESS>\n" +
+ " am dumpheap [flags] <PROCESS> <FILE>\n" +
+ " am monitor [--gdb <port>]\n" +
+ " am screen-compat [on|off] <PACKAGE>\n" +
+ " am display-size [reset|MxN]\n" +
"\n" +
- " start an Activity: am start [-D] [-W] <INTENT>\n" +
- " -D: enable debugging\n" +
- " -W: wait for launch to complete\n" +
+ "am start: start an Activity. Options are:\n" +
+ " -D: enable debugging\n" +
+ " -W: wait for launch to complete\n" +
"\n" +
- " start a Service: am startservice <INTENT>\n" +
+ "am startservice: start a Service.\n" +
"\n" +
- " force stop everything associated with a package: force-stop <package>\n" +
+ "am force-stop: force stop everything associated with <PACKAGE>.\n" +
"\n" +
- " send a broadcast Intent: am broadcast <INTENT>\n" +
+ "am broadcast: send a broadcast Intent.\n" +
"\n" +
- " start an Instrumentation: am instrument [flags] <COMPONENT>\n" +
- " -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)\n" +
- " -e <NAME> <VALUE>: set argument <NAME> to <VALUE>\n" +
- " -p <FILE>: write profiling data to <FILE>\n" +
- " -w: wait for instrumentation to finish before returning\n" +
+ "am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" +
+ " is the form <TEST_PACKAGE>/<RUNNER_CLASS>. Options are:\n" +
+ " -r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT). Use with\n" +
+ " [-e perf true] to generate raw output for performance measurements.\n" +
+ " -e <NAME> <VALUE>: set argument <NAME> to <VALUE>. For test runners a\n" +
+ " common form is [-e <testrunner_flag> <value>[,<value>...]].\n" +
+ " -p <FILE>: write profiling data to <FILE>\n" +
+ " -w: wait for instrumentation to finish before returning. Required for\n" +
+ " test runners.\n" +
+ " --no-window-animation: turn off window animations will running.\n" +
"\n" +
- " run a test package against an application: am instrument [flags] <TEST_PACKAGE>/<RUNNER_CLASS>\n" +
- " -e <testrunner_flag> <testrunner_value> [,<testrunner_value>]\n" +
- " -w wait for the test to finish (required)\n" +
- " -r use with -e perf true to generate raw output for performance measurements\n" +
+ "am profile: start and stop profiler on a process.\n" +
"\n" +
- " start profiling: am profile <PROCESS> start <FILE>\n" +
- " stop profiling: am profile <PROCESS> stop\n" +
- " dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
- " -n: dump native heap instead of managed heap\n" +
+ "am dumpheap: dump the heap of a process. Options are:\n" +
+ " -n: dump native heap instead of managed heap\n" +
"\n" +
- " start monitoring: am monitor [--gdb <port>]\n" +
- " --gdb: start gdbserv on the given port at crash/ANR\n" +
+ "am monitor: start monitoring for crashes or ANRs.\n" +
+ " --gdb: start gdbserv on the given port at crash/ANR\n" +
"\n" +
- " control screen compatibility: am screen-compat [on|off] [package]\n" +
+ "am screen-compat: control screen compatibility mode of <PACKAGE>.\n" +
"\n" +
- " override display size: am display-size [reset|MxN]\n" +
+ "am display-size: override display size.\n" +
"\n" +
- " <INTENT> specifications include these flags:\n" +
- " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
- " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
- " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
- " [--esn <EXTRA_KEY> ...]\n" +
- " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
- " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
- " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
- " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
- " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
- " [-n <COMPONENT>] [-f <FLAGS>]\n" +
- " [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
- " [--debug-log-resolution]\n" +
- " [--activity-brought-to-front] [--activity-clear-top]\n" +
- " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
- " [--activity-launched-from-history] [--activity-multiple-task]\n" +
- " [--activity-no-animation] [--activity-no-history]\n" +
- " [--activity-no-user-action] [--activity-previous-is-top]\n" +
- " [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
- " [--activity-single-top]\n" +
- " [--receiver-registered-only] [--receiver-replace-pending]\n" +
- " [<URI>]\n"
+ "<INTENT> specifications include these flags and arguments:\n" +
+ " [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
+ " [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
+ " [-e|--es <EXTRA_KEY> <EXTRA_STRING_VALUE> ...]\n" +
+ " [--esn <EXTRA_KEY> ...]\n" +
+ " [--ez <EXTRA_KEY> <EXTRA_BOOLEAN_VALUE> ...]\n" +
+ " [--ei <EXTRA_KEY> <EXTRA_INT_VALUE> ...]\n" +
+ " [--el <EXTRA_KEY> <EXTRA_LONG_VALUE> ...]\n" +
+ " [--eia <EXTRA_KEY> <EXTRA_INT_VALUE>[,<EXTRA_INT_VALUE...]]\n" +
+ " [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
+ " [-n <COMPONENT>] [-f <FLAGS>]\n" +
+ " [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
+ " [--debug-log-resolution] [--exclude-stopped-packages]\n" +
+ " [--include-stopped-packages]\n" +
+ " [--activity-brought-to-front] [--activity-clear-top]\n" +
+ " [--activity-clear-when-task-reset] [--activity-exclude-from-recents]\n" +
+ " [--activity-launched-from-history] [--activity-multiple-task]\n" +
+ " [--activity-no-animation] [--activity-no-history]\n" +
+ " [--activity-no-user-action] [--activity-previous-is-top]\n" +
+ " [--activity-reorder-to-front] [--activity-reset-task-if-needed]\n" +
+ " [--activity-single-top] [--activity-clear-task]\n" +
+ " [--activity-task-on-home]\n" +
+ " [--receiver-registered-only] [--receiver-replace-pending]\n" +
+ " [<URI>]\n"
);
}
}
diff --git a/cmds/keystore/keystore.cpp b/cmds/keystore/keystore.cpp
index bbd1a1b..1c1f37a 100644
--- a/cmds/keystore/keystore.cpp
+++ b/cmds/keystore/keystore.cpp
@@ -708,7 +708,7 @@
uid_t euid;
uint32_t perms;
} users[] = {
- {AID_SYSTEM, ~0, ~GET},
+ {AID_SYSTEM, ~0, ~0},
{AID_VPN, AID_SYSTEM, GET},
{AID_WIFI, AID_SYSTEM, GET},
{AID_ROOT, AID_SYSTEM, GET},
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index e433079..c980715 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -125,12 +125,12 @@
return;
}
- if ("setInstallLocation".equals(op)) {
+ if ("set-install-location".equals(op)) {
runSetInstallLocation();
return;
}
- if ("getInstallLocation".equals(op)) {
+ if ("get-install-location".equals(op)) {
runGetInstallLocation();
return;
}
@@ -1094,8 +1094,7 @@
}
private static void showUsage() {
- System.err.println("usage: pm [list|path|install|uninstall]");
- System.err.println(" pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]");
+ System.err.println("usage: pm list packages [-f] [-d] [-e] [-s] [-e] [-u] [FILTER]");
System.err.println(" pm list permission-groups");
System.err.println(" pm list permissions [-g] [-f] [-d] [-u] [GROUP]");
System.err.println(" pm list instrumentation [-f] [TARGET-PACKAGE]");
@@ -1107,66 +1106,66 @@
System.err.println(" pm clear PACKAGE");
System.err.println(" pm enable PACKAGE_OR_COMPONENT");
System.err.println(" pm disable PACKAGE_OR_COMPONENT");
- System.err.println(" pm setInstallLocation [0/auto] [1/internal] [2/external]");
+ System.err.println(" pm disable-user PACKAGE_OR_COMPONENT");
+ System.err.println(" pm set-install-location [0/auto] [1/internal] [2/external]");
+ System.err.println(" pm get-install-location");
System.err.println(" pm createUser USER_NAME");
System.err.println(" pm removeUser USER_ID");
System.err.println("");
- System.err.println("The list packages command prints all packages, optionally only");
- System.err.println("those whose package name contains the text in FILTER. Options:");
- System.err.println(" -f: see their associated file.");
- System.err.println(" -d: filter to only show disbled packages.");
- System.err.println(" -e: filter to only show enabled packages.");
- System.err.println(" -s: filter to only show system packages.");
- System.err.println(" -3: filter to only show third party packages.");
- System.err.println(" -u: also include uninstalled packages.");
+ System.err.println("pm list packages: prints all packages, optionally only");
+ System.err.println(" those whose package name contains the text in FILTER. Options:");
+ System.err.println(" -f: see their associated file.");
+ System.err.println(" -d: filter to only show disbled packages.");
+ System.err.println(" -e: filter to only show enabled packages.");
+ System.err.println(" -s: filter to only show system packages.");
+ System.err.println(" -3: filter to only show third party packages.");
+ System.err.println(" -u: also include uninstalled packages.");
System.err.println("");
- System.err.println("The list permission-groups command prints all known");
- System.err.println("permission groups.");
+ System.err.println("pm list permission-groups: prints all known permission groups.");
System.err.println("");
- System.err.println("The list permissions command prints all known");
- System.err.println("permissions, optionally only those in GROUP. Options:");
- System.err.println(" -g: organize by group.");
- System.err.println(" -f: print all information.");
- System.err.println(" -s: short summary.");
- System.err.println(" -d: only list dangerous permissions.");
- System.err.println(" -u: list only the permissions users will see.");
+ System.err.println("pm list permissions: prints all known permissions, optionally only");
+ System.err.println(" those in GROUP. Options:");
+ System.err.println(" -g: organize by group.");
+ System.err.println(" -f: print all information.");
+ System.err.println(" -s: short summary.");
+ System.err.println(" -d: only list dangerous permissions.");
+ System.err.println(" -u: list only the permissions users will see.");
System.err.println("");
- System.err.println("The list instrumentation command prints all instrumentations,");
- System.err.println("or only those that target a specified package. Options:");
- System.err.println(" -f: see their associated file.");
- System.err.println("(Use this command to list all test packages, or use <TARGET-PACKAGE> ");
- System.err.println(" to list the test packages for a particular application. The -f ");
- System.err.println(" option lists the .apk file for the test package.)");
+ System.err.println("pm list instrumentation: use to list all test packages; optionally");
+ System.err.println(" supply <TARGET-PACKAGE> to list the test packages for a particular");
+ System.err.println(" application. Options:");
+ System.err.println(" -f: list the .apk file for the test package.");
System.err.println("");
- System.err.println("The list features command prints all features of the system.");
+ System.err.println("pm list features: prints all features of the system.");
System.err.println("");
- System.err.println("The path command prints the path to the .apk of a package.");
+ System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
System.err.println("");
- System.err.println("The install command installs a package to the system. Options:");
- System.err.println(" -l: install the package with FORWARD_LOCK.");
- System.err.println(" -r: reinstall an exisiting app, keeping its data.");
- System.err.println(" -t: allow test .apks to be installed.");
- System.err.println(" -i: specify the installer package name.");
- System.err.println(" -s: install package on sdcard.");
- System.err.println(" -f: install package on internal flash.");
+ System.err.println("pm install: installs a package to the system. Options:");
+ System.err.println(" -l: install the package with FORWARD_LOCK.");
+ System.err.println(" -r: reinstall an exisiting app, keeping its data.");
+ System.err.println(" -t: allow test .apks to be installed.");
+ System.err.println(" -i: specify the installer package name.");
+ System.err.println(" -s: install package on sdcard.");
+ System.err.println(" -f: install package on internal flash.");
System.err.println("");
- System.err.println("The uninstall command removes a package from the system. Options:");
- System.err.println(" -k: keep the data and cache directories around.");
- System.err.println("after the package removal.");
+ System.err.println("pm uninstall: removes a package from the system. Options:");
+ System.err.println(" -k: keep the data and cache directories around after package removal.");
System.err.println("");
- System.err.println("The clear command deletes all data associated with a package.");
+ System.err.println("pm clear: deletes all data associated with a package.");
System.err.println("");
- System.err.println("The enable and disable commands change the enabled state of");
- System.err.println("a given package or component (written as \"package/class\").");
+ System.err.println("pm enable, disable, disable-user: these commands change the enabled state");
+ System.err.println(" of a given package or component (written as \"package/class\").");
System.err.println("");
- System.err.println("The getInstallLocation command gets the current install location");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
+ System.err.println("pm get-install-location: returns the current install location.");
+ System.err.println(" 0 [auto]: Let system decide the best location");
+ System.err.println(" 1 [internal]: Install on internal device storage");
+ System.err.println(" 2 [external]: Install on external media");
System.err.println("");
- System.err.println("The setInstallLocation command changes the default install location");
- System.err.println(" 0 [auto]: Let system decide the best location");
- System.err.println(" 1 [internal]: Install on internal device storage");
- System.err.println(" 2 [external]: Install on external media");
+ System.err.println("pm set-install-location: changes the default install location.");
+ System.err.println(" NOTE: this is only intended for debugging; using this can cause");
+ System.err.println(" applications to break and other undersireable behavior.");
+ System.err.println(" 0 [auto]: Let system decide the best location");
+ System.err.println(" 1 [internal]: Install on internal device storage");
+ System.err.println(" 2 [external]: Install on external media");
}
}
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/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/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index a8c31f9..b993bd8 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1115,6 +1115,9 @@
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener);
return true;
+ } else if (profile == BluetoothProfile.HEALTH) {
+ BluetoothHealth health = new BluetoothHealth(context, listener);
+ return true;
} else {
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHealth.java b/core/java/android/bluetooth/BluetoothHealth.java
new file mode 100644
index 0000000..52efc07
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealth.java
@@ -0,0 +1,444 @@
+/*
+ * 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.bluetooth;
+
+import android.annotation.SdkConstant;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API for Bluetooth Health Profile.
+ *
+ * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
+ * Service via IPC.
+ *
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHealth proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
+ * @hide
+ */
+public final class BluetoothHealth implements BluetoothProfile {
+ private static final String TAG = "BluetoothHealth";
+ private static final boolean DBG = false;
+
+ /**
+ * Health Profile Source Role - the health device.
+ */
+ public static final int SOURCE_ROLE = 1 << 0;
+
+ /**
+ * Health Profile Sink Role the device talking to the health device.
+ */
+ public static final int SINK_ROLE = 1 << 1;
+
+ /**
+ * Health Profile - Channel Type used - Reliable
+ */
+ public static final int CHANNEL_TYPE_RELIABLE = 10;
+
+ /**
+ * Health Profile - Channel Type used - Streaming
+ */
+ public static final int CHANNEL_TYPE_STREAMING = 11;
+
+ /**
+ * @hide
+ */
+ public static final int CHANNEL_TYPE_ANY = 12;
+
+ private final ArrayList<BluetoothHealthAppConfiguration> mAppConfigs =
+ new ArrayList<BluetoothHealthAppConfiguration>();
+
+ /**
+ * Register an application configuration that acts as a Health SINK.
+ * This is the configuration that will be used to communicate with health devices
+ * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
+ * the callback is used to notify success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param name The friendly name associated with the application or configuration.
+ * @param dataType The dataType of the Source role of Health Profile to which
+ * the sink wants to connect to.
+ * @param callback A callback to indicate success or failure of the registration and
+ * all operations done on this application configuration.
+ * @return If true, callback will be called.
+ */
+ public boolean registerSinkAppConfiguration(String name, int dataType,
+ IBluetoothHealthCallback callback) {
+ if (!isEnabled() || name == null) return false;
+
+ if (DBG) log("registerSinkApplication(" + name + ":" + dataType + ")");
+ return registerAppConfiguration(name, dataType, SINK_ROLE,
+ CHANNEL_TYPE_ANY, callback);
+ }
+
+ /**
+ * Register an application configuration that acts as a Health SINK or in a Health
+ * SOURCE role.This is an asynchronous call and so
+ * the callback is used to notify success or failure if the function returns true.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param name The friendly name associated with the application or configuration.
+ * @param dataType The dataType of the Source role of Health Profile.
+ * @param channelType The channel type. Will be one of
+ * {@link #CHANNEL_TYPE_RELIABLE} or
+ * {@link #CHANNEL_TYPE_STREAMING}
+ * @param callback - A callback to indicate success or failure.
+ * @return If true, callback will be called.
+ * @hide
+ */
+ public boolean registerAppConfiguration(String name, int dataType, int role,
+ int channelType, IBluetoothHealthCallback callback) {
+ boolean result = false;
+ if (!isEnabled() || !checkAppParam(name, role, channelType, callback)) return result;
+
+ if (DBG) log("registerApplication(" + name + ":" + dataType + ")");
+ BluetoothHealthAppConfiguration config =
+ new BluetoothHealthAppConfiguration(name, dataType, role, channelType,
+ callback);
+
+ if (mService != null) {
+ try {
+ result = mService.registerAppConfiguration(config);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+
+ if (result) mAppConfigs.add(config);
+ return result;
+ }
+
+ /**
+ * Unregister an application configuration that has been registered using
+ * {@link #registerSinkAppConfiguration}
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param config The health app configuration
+ * @return Success or failure.
+ * @hide
+ */
+ public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+ boolean result = false;
+ if (mService != null && isEnabled() && isValidAppConfig(config)) {
+ try {
+ result = mService.unregisterAppConfiguration(config);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ if (result) mAppConfigs.remove(config);
+ return result;
+ }
+
+ /**
+ * Connect to a health device which has the {@link #SOURCE_ROLE}.
+ * This is an asynchrnous call. If this function returns true, the callback
+ * associated with the application configuration will be called.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote Bluetooth device.
+ * @param config The application configuration which has been registed using
+ * {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+ * @return If true, the callback associated with the application config will be called.
+ */
+ public boolean connectChannelToSource(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ if (mService != null && isEnabled() && isValidDevice(device) &&
+ isValidAppConfig(config)) {
+ try {
+ return mService.connectChannelToSource(device, config);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Connect to a health device which has the {@link #SINK_ROLE}.
+ * This is an asynchronous call. If this function returns true, the callback
+ * associated with the application configuration will be called.
+ *
+ *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote Bluetooth device.
+ * @param config The application configuration which has been registed using
+ * {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+ * @return If true, the callback associated with the application config will be called.
+ * @hide
+ */
+ public boolean connectChannelToSink(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, int channelType) {
+ if (mService != null && isEnabled() && isValidDevice(device) &&
+ isValidAppConfig(config)) {
+ try {
+ return mService.connectChannelToSink(device, config, channelType);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Disconnect a connected health channel.
+ * This is an asynchronous call. If this function returns true, the callback
+ * associated with the application configuration will be called.
+ *
+ *<p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote Bluetooth device.
+ * @param config The application configuration which has been registed using
+ * {@link #registerSinkAppConfiguration(String, int, IBluetoothHealthCallback) }
+ * @param fd The file descriptor that was associated with the channel.
+ * @return If true, the callback associated with the application config will be called.
+ * @hide
+ */
+ public boolean disconnectChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+ if (mService != null && isEnabled() && isValidDevice(device) &&
+ isValidAppConfig(config)) {
+ try {
+ return mService.disconnectChannel(device, config, fd);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Get the file descriptor of the main channel associated with the remote device
+ * and application configuration.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * @param device The remote Bluetooth health device
+ * @param config The application configuration
+ * @return null on failure, ParcelFileDescriptor on success.
+ */
+
+ public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ if (mService != null && isEnabled() && isValidDevice(device) &&
+ isValidAppConfig(config)) {
+ try {
+ return mService.getMainChannelFd(device, config);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return null;
+ }
+
+ /**
+ * Get the current connection state of the profile.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * This is not specific to any application configuration but represents the connection
+ * state of the local Bluetooth adapter with the remote device. This can be used
+ * by applications like status bar which would just like to know the state of the
+ * local adapter.
+ *
+ * @param device Remote bluetooth device.
+ * @return State of the profile connection. One of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+ */
+ public int getConnectionState(BluetoothDevice device) {
+ if (mService != null && isEnabled() && isValidDevice(device)) {
+ try {
+ return mService.getHealthDeviceConnectionState(device);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return STATE_DISCONNECTED;
+ }
+
+ /**
+ * Get connected devices for this specific profile.
+ *
+ * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * This is not specific to any application configuration but represents the connection
+ * state of the local Bluetooth adapter for this profile. This can be used
+ * by applications like status bar which would just like to know the state of the
+ * local adapter.
+ * @return List of devices. The list will be empty on error.
+ */
+ public List<BluetoothDevice> getConnectedDevices() {
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getConnectedHealthDevices();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ }
+
+ /**
+ * Get a list of devices that match any of the given connection
+ * states.
+ *
+ * <p> If none of the devices match any of the given states,
+ * an empty list will be returned.
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+ * This is not specific to any application configuration but represents the connection
+ * state of the local Bluetooth adapter for this profile. This can be used
+ * by applications like status bar which would just like to know the state of the
+ * local adapter.
+ *
+ * @param states Array of states. States can be one of
+ * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+ * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+ * @return List of devices. The list will be empty on error.
+ */
+ public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+ if (mService != null && isEnabled()) {
+ try {
+ return mService.getHealthDevicesMatchingConnectionStates(states);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+ if (mService == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ }
+
+ /** Health Channel Connection State - Disconnected */
+ public static final int STATE_CHANNEL_DISCONNECTED = 0;
+ /** Health Channel Connection State - Connecting */
+ public static final int STATE_CHANNEL_CONNECTING = 1;
+ /** Health Channel Connection State - Connected */
+ public static final int STATE_CHANNEL_CONNECTED = 2;
+ /** Health Channel Connection State - Disconnecting */
+ public static final int STATE_CHANNEL_DISCONNECTING = 3;
+
+ /** Health App Configuration registration success */
+ public static final int APPLICATION_REGISTRATION_SUCCESS = 0;
+ /** Health App Configuration registration failure */
+ public static final int APPLICATION_REGISTRATION_FAILURE = 1;
+ /** Health App Configuration un-registration success */
+ public static final int APPLICATION_UNREGISTRATION_SUCCESS = 2;
+ /** Health App Configuration un-registration failure */
+ public static final int APPLICATION_UNREGISTRATION_FAILURE = 3;
+
+ private Context mContext;
+ private ServiceListener mServiceListener;
+ private IBluetooth mService;
+ BluetoothAdapter mAdapter;
+
+ /**
+ * Create a BluetoothHealth proxy object.
+ */
+ /*package*/ BluetoothHealth(Context mContext, ServiceListener l) {
+ IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE);
+ mServiceListener = l;
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (b != null) {
+ mService = IBluetooth.Stub.asInterface(b);
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected(BluetoothProfile.HEALTH, this);
+ }
+ } else {
+ Log.w(TAG, "Bluetooth Service not available!");
+
+ // Instead of throwing an exception which prevents people from going
+ // into Wireless settings in the emulator. Let it crash later when it is actually used.
+ mService = null;
+ }
+ }
+
+ private boolean isEnabled() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ log("Bluetooth is Not enabled");
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private boolean isValidAppConfig(BluetoothHealthAppConfiguration config) {
+ if (!mAppConfigs.isEmpty() && mAppConfigs.contains(config)) return true;
+ log("Not a valid config: " + config);
+ return false;
+ }
+
+ private boolean checkAppParam(String name, int role, int channelType,
+ IBluetoothHealthCallback callback) {
+ if (name == null || (role != SOURCE_ROLE && role != SINK_ROLE) ||
+ (channelType != CHANNEL_TYPE_RELIABLE &&
+ channelType != CHANNEL_TYPE_STREAMING &&
+ channelType != CHANNEL_TYPE_ANY) || callback == null) {
+ return false;
+ }
+ if (role == SOURCE_ROLE && channelType == CHANNEL_TYPE_ANY) return false;
+ return true;
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl
new file mode 100644
index 0000000..bc9e54f
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+** 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.
+*/
+
+package android.bluetooth;
+
+parcelable BluetoothHealthAppConfiguration;
diff --git a/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
new file mode 100644
index 0000000..b87aea5
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHealthAppConfiguration.java
@@ -0,0 +1,180 @@
+/*
+ * 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.bluetooth;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The Bluetooth Health Application Configuration that is used in conjunction with
+ * the {@link BluetoothHealth} class. This class represents an application configuration
+ * that the Bluetooth Health third party application will register to communicate with the
+ * remote Bluetooth health device.
+ *
+ * @hide
+ */
+public final class BluetoothHealthAppConfiguration implements Parcelable {
+ private final String mName;
+ private final int mDataType;
+ private final int mRole;
+ private final int mChannelType;
+ private final IBluetoothHealthCallback mCallback;
+
+ /**
+ * Constructor to register the SINK role
+ *
+ * @param name Friendly name associated with the application configuration
+ * @param dataType Data Type of the remote Bluetooth Health device
+ * @param callback Callback associated with the application configuration.
+ */
+ BluetoothHealthAppConfiguration(String name, int dataType, IBluetoothHealthCallback callback) {
+ mName = name;
+ mDataType = dataType;
+ mRole = BluetoothHealth.SINK_ROLE;
+ mChannelType = BluetoothHealth.CHANNEL_TYPE_ANY;
+ mCallback = callback;
+ }
+
+ /**
+ * Constructor to register the application configuration.
+ *
+ * @param name Friendly name associated with the application configuration
+ * @param dataType Data Type of the remote Bluetooth Health device
+ * @param role {@link BluetoothHealth.SOURCE_ROLE} or
+ * {@link BluetoothHealth.SINK_ROLE}
+ * @param callback Callback associated with the application configuration.
+ */
+ BluetoothHealthAppConfiguration(String name, int dataType, int role, int channelType,
+ IBluetoothHealthCallback callback) {
+ mName = name;
+ mDataType = dataType;
+ mRole = role;
+ mChannelType = channelType;
+ mCallback = callback;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof BluetoothHealthAppConfiguration) {
+ BluetoothHealthAppConfiguration config = (BluetoothHealthAppConfiguration) o;
+ // config.getName() can never be NULL
+ return mName.equals(config.getName()) &&
+ mDataType == config.getDataType() &&
+ mRole == config.getRole() &&
+ mChannelType == config.getChannelType() &&
+ mCallback.equals(config.getCallback());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (mName != null ? mName.hashCode() : 0);
+ result = 31 * result + mDataType;
+ result = 31 * result + mRole;
+ result = 31 * result + mChannelType;
+ result = 31 * result + (mCallback != null ? mCallback.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "BluetoothHealthAppConfiguration [mName = " + mName +
+ ",mDataType = " + mDataType + ", mRole = " + mRole + ",mChannelType = " +
+ mChannelType + ",callback=" + mCallback +"]";
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Return the data type associated with this application configuration.
+ *
+ * @return dataType
+ */
+ public int getDataType() {
+ return mDataType;
+ }
+
+ /**
+ * Return the name of the application configuration.
+ *
+ * @return String name
+ */
+ public String getName() {
+ return mName;
+ }
+
+ /**
+ * Return the role associated with this application configuration.
+ *
+ * @return One of {@link BluetoothHealth#SOURCE_ROLE} or
+ * {@link BluetoothHealth#SINK_ROLE}
+ */
+ public int getRole() {
+ return mRole;
+ }
+
+ /**
+ * Return the channel type associated with this application configuration.
+ *
+ * @return One of {@link BluetoothHealth#CHANNEL_TYPE_RELIABLE} or
+ * {@link BluetoothHealth#CHANNEL_TYPE_STREAMING} or
+ * {@link BluetoothHealth#CHANNEL_TYPE_ANY}.
+ */
+ public int getChannelType() {
+ return mChannelType;
+ }
+
+ /**
+ * Return the callback associated with this application configuration.
+ *
+ * @return IBluetoothHealthCallback
+ */
+ public IBluetoothHealthCallback getCallback() {
+ return mCallback;
+ }
+
+ public static final Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR =
+ new Parcelable.Creator<BluetoothHealthAppConfiguration>() {
+ public BluetoothHealthAppConfiguration createFromParcel(Parcel in) {
+ String name = in.readString();
+ int type = in.readInt();
+ int role = in.readInt();
+ int channelType = in.readInt();
+ IBluetoothHealthCallback callback =
+ IBluetoothHealthCallback.Stub.asInterface(in.readStrongBinder());
+ return new BluetoothHealthAppConfiguration(name, type, role, channelType,
+ callback);
+ }
+ public BluetoothHealthAppConfiguration[] newArray(int size) {
+ return new BluetoothHealthAppConfiguration[size];
+ }
+ };
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeString(mName);
+ out.writeInt(mDataType);
+ out.writeInt(mRole);
+ out.writeInt(mChannelType);
+ out.writeStrongInterface(mCallback);
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 22555f0..6cd81fd 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -65,16 +65,22 @@
public static final int A2DP = 2;
/**
+ * Health Profile
+ * @hide
+ */
+ public static final int HEALTH = 3;
+
+ /**
* Input Device Profile
* @hide
*/
- public static final int INPUT_DEVICE = 3;
+ public static final int INPUT_DEVICE = 4;
/**
* PAN Profile
* @hide
*/
- public static final int PAN = 4;
+ public static final int PAN = 5;
/**
* Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index d25f5d0..28b09b6 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -18,7 +18,9 @@
import android.bluetooth.IBluetoothCallback;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealthAppConfiguration;
import android.os.ParcelUuid;
+import android.os.ParcelFileDescriptor;
/**
* System private API for talking with the Bluetooth service.
@@ -98,5 +100,17 @@
boolean connectPanDevice(in BluetoothDevice device);
boolean disconnectPanDevice(in BluetoothDevice device);
+ // HDP profile APIs
+ boolean registerAppConfiguration(in BluetoothHealthAppConfiguration config);
+ boolean unregisterAppConfiguration(in BluetoothHealthAppConfiguration config);
+ boolean connectChannelToSource(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+ boolean connectChannelToSink(in BluetoothDevice device, in BluetoothHealthAppConfiguration config,
+ int channelType);
+ boolean disconnectChannel(in BluetoothDevice device, in BluetoothHealthAppConfiguration config, in ParcelFileDescriptor fd);
+ ParcelFileDescriptor getMainChannelFd(in BluetoothDevice device, in BluetoothHealthAppConfiguration config);
+ List<BluetoothDevice> getConnectedHealthDevices();
+ List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(in int[] states);
+ int getHealthDeviceConnectionState(in BluetoothDevice device);
+
void sendConnectionStateChange(in BluetoothDevice device, int state, int prevState);
}
diff --git a/core/java/android/bluetooth/IBluetoothHealthCallback.aidl b/core/java/android/bluetooth/IBluetoothHealthCallback.aidl
new file mode 100644
index 0000000..9fe5335
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHealthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.os.ParcelFileDescriptor;
+
+/**
+ *@hide
+ */
+interface IBluetoothHealthCallback
+{
+ void onHealthAppConfigurationStatusChange(in BluetoothHealthAppConfiguration config, int status);
+ void onHealthChannelStateChange(in BluetoothHealthAppConfiguration config,
+ in BluetoothDevice device, int prevState, int newState, in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 0be1776..8a42693 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -288,7 +288,8 @@
* you can call {@link #reconnect()} to reclaim the camera.
*
* <p>This must be done before calling
- * {@link android.media.MediaRecorder#setCamera(Camera)}.
+ * {@link android.media.MediaRecorder#setCamera(Camera)}. This cannot be
+ * called after recording starts.
*
* <p>If you are not recording video, you probably do not need this method.
*
@@ -301,6 +302,11 @@
* Camera objects are locked by default unless {@link #unlock()} is
* called. Normally {@link #reconnect()} is used instead.
*
+ * <p>Since API level 13, camera is automatically locked for applications in
+ * {@link android.media.MediaRecorder#start()}. Applications can use the
+ * camera (ex: zoom) after recording starts. There is no need to call this
+ * after recording starts or stops.
+ *
* <p>If you are not recording video, you probably do not need this method.
*
* @throws RuntimeException if the camera cannot be re-locked (for
@@ -315,9 +321,10 @@
* which will re-acquire the lock and allow you to continue using the
* camera.
*
- * <p>This must be done after {@link android.media.MediaRecorder} is
- * done recording if {@link android.media.MediaRecorder#setCamera(Camera)}
- * was used.
+ * <p>Since API level 13, camera is automatically locked for applications in
+ * {@link android.media.MediaRecorder#start()}. Applications can use the
+ * camera (ex: zoom) after recording starts. There is no need to call this
+ * after recording starts or stops.
*
* <p>If you are not recording video, you probably do not need this method.
*
@@ -827,7 +834,9 @@
* <p>This method is only valid when preview is active (after
* {@link #startPreview()}). Preview will be stopped after the image is
* taken; callers must call {@link #startPreview()} again if they want to
- * re-start preview or take more pictures.
+ * re-start preview or take more pictures. This should not be called between
+ * {@link android.media.MediaRecorder#start()} and
+ * {@link android.media.MediaRecorder#stop()}.
*
* <p>After calling this method, you must not call {@link #startPreview()}
* or take another picture until the JPEG callback has returned.
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/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/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/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index e72aaa7..a220007 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -989,6 +989,33 @@
BluetoothPan.LOCAL_NAP_ROLE);
}
+ /**
+ * Called by native code on a PropertyChanged signal from
+ * org.bluez.HealthDevice.
+ *
+ * @param devicePath the object path of the remote device
+ * @param propValues Properties (Name-Value) of the Health Device.
+ */
+ private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) {
+ log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]);
+ mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]);
+ }
+
+ /**
+ * Called by native code on a ChannelCreated/Deleted signal from
+ * org.bluez.HealthDevice.
+ *
+ * @param devicePath the object path of the remote device
+ * @param channelPath the path of the health channel.
+ * @param exists Boolean to indicate if the channel was created or deleted.
+ */
+ private void onHealthDeviceChannelChanged(String devicePath, String channelPath,
+ boolean exists) {
+ log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath +
+ ":exists" + exists);
+ mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists);
+ }
+
private void onRestartRequired() {
if (mBluetoothService.isEnabled()) {
Log.e(TAG, "*** A serious error occurred (did bluetoothd crash?) - " +
diff --git a/core/java/android/server/BluetoothHealthProfileHandler.java b/core/java/android/server/BluetoothHealthProfileHandler.java
new file mode 100644
index 0000000..7f862e0
--- /dev/null
+++ b/core/java/android/server/BluetoothHealthProfileHandler.java
@@ -0,0 +1,600 @@
+/*
+ * 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.server;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHealth;
+import android.bluetooth.BluetoothHealthAppConfiguration;
+import android.bluetooth.BluetoothHealth;
+import android.bluetooth.BluetoothInputDevice;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * This handles all the operations on the Bluetooth Health profile.
+ * All functions are called by BluetoothService, as Bluetooth Service
+ * is the Service handler for the HDP profile.
+ *
+ * @hide
+ */
+final class BluetoothHealthProfileHandler {
+ private static final String TAG = "BluetoothHealthProfileHandler";
+ /*STOPSHIP*/
+ private static final boolean DBG = true;
+
+ private static BluetoothHealthProfileHandler sInstance;
+ private Context mContext;
+ private BluetoothService mBluetoothService;
+ private ArrayList<HealthChannel> mHealthChannels;
+ private HashMap <BluetoothHealthAppConfiguration, String> mHealthAppConfigs;
+ private HashMap <BluetoothDevice, Integer> mHealthDevices;
+
+ private static final int MESSAGE_REGISTER_APPLICATION = 0;
+ private static final int MESSAGE_UNREGISTER_APPLICATION = 1;
+ private static final int MESSAGE_CONNECT_CHANNEL = 2;
+
+ class HealthChannel {
+ private ParcelFileDescriptor mChannelFd;
+ private boolean mMainChannel;
+ private String mChannelPath;
+ private BluetoothDevice mDevice;
+ private BluetoothHealthAppConfiguration mConfig;
+ private int mState;
+ private int mChannelType;
+
+ HealthChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config,
+ ParcelFileDescriptor fd, boolean mainChannel, String channelPath) {
+ mChannelFd = fd;
+ mMainChannel = mainChannel;
+ mChannelPath = channelPath;
+ mDevice = device;
+ mConfig = config;
+ mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ }
+ }
+
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_REGISTER_APPLICATION:
+ BluetoothHealthAppConfiguration registerApp =
+ (BluetoothHealthAppConfiguration) msg.obj;
+ int role = registerApp.getRole();
+ String path = null;
+
+ if (role == BluetoothHealth.SINK_ROLE) {
+ path = mBluetoothService.registerHealthApplicationNative(
+ registerApp.getDataType(), getStringRole(role), registerApp.getName());
+ } else {
+ path = mBluetoothService.registerHealthApplicationNative(
+ registerApp.getDataType(), getStringRole(role), registerApp.getName(),
+ getStringChannelType(registerApp.getChannelType()));
+ }
+
+ if (path == null) {
+ callHealthApplicationStatusCallback(registerApp,
+ BluetoothHealth.APPLICATION_REGISTRATION_FAILURE);
+ } else {
+ mHealthAppConfigs.put(registerApp, path);
+ callHealthApplicationStatusCallback(registerApp,
+ BluetoothHealth.APPLICATION_REGISTRATION_SUCCESS);
+ }
+
+ break;
+ case MESSAGE_UNREGISTER_APPLICATION:
+ BluetoothHealthAppConfiguration unregisterApp =
+ (BluetoothHealthAppConfiguration) msg.obj;
+ boolean result = mBluetoothService.unregisterHealthApplicationNative(
+ mHealthAppConfigs.get(unregisterApp));
+ if (result) {
+ callHealthApplicationStatusCallback(unregisterApp,
+ BluetoothHealth.APPLICATION_UNREGISTRATION_SUCCESS);
+ } else {
+ callHealthApplicationStatusCallback(unregisterApp,
+ BluetoothHealth.APPLICATION_UNREGISTRATION_FAILURE);
+ }
+ break;
+ case MESSAGE_CONNECT_CHANNEL:
+ HealthChannel chan = (HealthChannel)msg.obj;
+ String deviceObjectPath =
+ mBluetoothService.getObjectPathFromAddress(chan.mDevice.getAddress());
+ String configPath = mHealthAppConfigs.get(chan.mConfig);
+ String channelType = getStringChannelType(chan.mChannelType);
+
+ if (!mBluetoothService.createChannelNative(deviceObjectPath, configPath,
+ channelType)) {
+ int prevState = chan.mState;
+ int state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ callHealthChannelCallback(chan.mConfig, chan.mDevice, prevState, state, null);
+ mHealthChannels.remove(chan);
+ }
+ }
+ }
+ };
+
+ private BluetoothHealthProfileHandler(Context context, BluetoothService service) {
+ mContext = context;
+ mBluetoothService = service;
+ mHealthAppConfigs = new HashMap<BluetoothHealthAppConfiguration, String>();
+ mHealthChannels = new ArrayList<HealthChannel>();
+ mHealthDevices = new HashMap<BluetoothDevice, Integer>();
+ }
+
+ static synchronized BluetoothHealthProfileHandler getInstance(Context context,
+ BluetoothService service) {
+ if (sInstance == null) sInstance = new BluetoothHealthProfileHandler(context, service);
+ return sInstance;
+ }
+
+ boolean registerAppConfiguration(BluetoothHealthAppConfiguration config) {
+ Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_APPLICATION);
+ msg.obj = config;
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+ String path = mHealthAppConfigs.get(config);
+ if (path == null) return false;
+
+ Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_APPLICATION);
+ msg.obj = config;
+ mHandler.sendMessage(msg);
+ return true;
+ }
+
+ boolean connectChannelToSource(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ return connectChannel(device, config, BluetoothHealth.CHANNEL_TYPE_ANY);
+ }
+
+ private HealthChannel getMainChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ for (HealthChannel chan: mHealthChannels) {
+ if (chan.mDevice.equals(device) && chan.mConfig.equals(config)) {
+ if (chan.mMainChannel) return chan;
+ }
+ }
+ return null;
+ }
+
+ boolean connectChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, int channelType) {
+ String deviceObjectPath =
+ mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ if (deviceObjectPath == null) return false;
+
+ String configPath = mHealthAppConfigs.get(config);
+ if (configPath == null) return false;
+
+ HealthChannel chan = new HealthChannel(device, config, null, false, null);
+ chan.mState = BluetoothHealth.STATE_CHANNEL_CONNECTING;
+ chan.mChannelType = channelType;
+ mHealthChannels.add(chan);
+
+ int prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ int state = BluetoothHealth.STATE_CHANNEL_CONNECTING;
+ callHealthChannelCallback(config, device, prevState, state, null);
+
+ Message msg = mHandler.obtainMessage(MESSAGE_CONNECT_CHANNEL);
+ msg.obj = chan;
+ mHandler.sendMessage(msg);
+
+ return true;
+ }
+
+ private String getStringChannelType(int type) {
+ if (type == BluetoothHealth.CHANNEL_TYPE_RELIABLE) {
+ return "Reliable";
+ } else if (type == BluetoothHealth.CHANNEL_TYPE_STREAMING) {
+ return "Streaming";
+ } else {
+ return "Any";
+ }
+ }
+
+ private String getStringRole(int role) {
+ if (role == BluetoothHealth.SINK_ROLE) {
+ return "Sink";
+ } else if (role == BluetoothHealth.SOURCE_ROLE) {
+ return "Streaming";
+ } else {
+ return null;
+ }
+ }
+
+ boolean disconnectChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+ HealthChannel chan = findChannelByFd(device, config, fd);
+ if (chan == null) return false;
+
+ String deviceObjectPath =
+ mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ if (mBluetoothService.destroyChannelNative(deviceObjectPath, chan.mChannelPath)) {
+ int prevState = chan.mState;
+ chan.mState = BluetoothHealth.STATE_CHANNEL_DISCONNECTING;
+ callHealthChannelCallback(config, device, prevState, chan.mState,
+ chan.mChannelFd);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private HealthChannel findChannelByFd(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+ for (HealthChannel chan : mHealthChannels) {
+ if (chan.mChannelFd.equals(fd) && chan.mDevice.equals(device) &&
+ chan.mConfig.equals(config)) return chan;
+ }
+ return null;
+ }
+
+ private HealthChannel findChannelByPath(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, String path) {
+ for (HealthChannel chan : mHealthChannels) {
+ if (chan.mChannelPath.equals(path) && chan.mDevice.equals(device) &&
+ chan.mConfig.equals(config)) return chan;
+ }
+ return null;
+ }
+
+ private List<HealthChannel> findChannelByStates(BluetoothDevice device, int[] states) {
+ List<HealthChannel> channels = new ArrayList<HealthChannel>();
+ for (HealthChannel chan: mHealthChannels) {
+ if (chan.mDevice.equals(device)) {
+ for (int state : states) {
+ if (chan.mState == state) {
+ channels.add(chan);
+ }
+ }
+ }
+ }
+ return channels;
+ }
+
+ private HealthChannel findConnectingChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ for (HealthChannel chan : mHealthChannels) {
+ if (chan.mDevice.equals(device) && chan.mConfig.equals(config) &&
+ chan.mState == BluetoothHealth.STATE_CHANNEL_CONNECTING) return chan;
+ }
+ return null;
+ }
+
+ ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ HealthChannel chan = getMainChannel(device, config);
+ if (chan != null) return chan.mChannelFd;
+
+ String objectPath =
+ mBluetoothService.getObjectPathFromAddress(device.getAddress());
+ if (objectPath == null) return null;
+
+ String mainChannelPath = mBluetoothService.getMainChannelNative(objectPath);
+ if (mainChannelPath == null) return null;
+
+ // We had no record of the main channel but querying Bluez we got a
+ // main channel. We might not have received the PropertyChanged yet for
+ // the main channel creation so update our data structure here.
+ chan = findChannelByPath(device, config, mainChannelPath);
+ if (chan == null) {
+ errorLog("Main Channel present but we don't have any account of it:" +
+ device +":" + config);
+ return null;
+ }
+ chan.mMainChannel = true;
+ return chan.mChannelFd;
+ }
+
+ /*package*/ void onHealthDevicePropertyChanged(String devicePath,
+ String channelPath) {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ String address = mBluetoothService.getAddressFromObjectPath(devicePath);
+ if (address == null) return;
+
+ //TODO: Fix this in Bluez
+ if (channelPath.equals("/")) {
+ // This means that the main channel is being destroyed.
+ return;
+ }
+
+ BluetoothDevice device = adapter.getRemoteDevice(address);
+ BluetoothHealthAppConfiguration config = findHealthApplication(device,
+ channelPath);
+ if (config != null) {
+ HealthChannel chan = findChannelByPath(device, config, channelPath);
+ if (chan == null) {
+ errorLog("Health Channel is not present:" + channelPath);
+ } else {
+ chan.mMainChannel = true;
+ }
+ }
+ }
+
+ private BluetoothHealthAppConfiguration findHealthApplication(
+ BluetoothDevice device, String channelPath) {
+ BluetoothHealthAppConfiguration config = null;
+ String configPath = mBluetoothService.getChannelApplicationNative(channelPath);
+
+ if (configPath == null) {
+ errorLog("No associated application for Health Channel:" + channelPath);
+ return null;
+ } else {
+ for (Entry<BluetoothHealthAppConfiguration, String> e :
+ mHealthAppConfigs.entrySet()) {
+ if (e.getValue().equals(configPath)) {
+ config = e.getKey();
+ }
+ }
+ if (config == null) {
+ errorLog("No associated application for application path:" + configPath);
+ return null;
+ }
+ }
+ return config;
+ }
+
+ /*package*/ void onHealthDeviceChannelChanged(String devicePath,
+ String channelPath, boolean exists) {
+ debugLog("onHealthDeviceChannelChanged: devicePath: " + devicePath +
+ "ChannelPath: " + channelPath + "Exists: " + exists);
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ String address = mBluetoothService.getAddressFromObjectPath(devicePath);
+ if (address == null) return;
+
+ BluetoothDevice device = adapter.getRemoteDevice(address);
+
+ BluetoothHealthAppConfiguration config = findHealthApplication(device,
+ channelPath);
+ int state, prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ ParcelFileDescriptor fd;
+ HealthChannel channel;
+
+ if (config != null) {
+ if (exists) {
+ fd = mBluetoothService.getChannelFdNative(channelPath);
+
+ if (fd == null) {
+ errorLog("Error obtaining fd for channel:" + channelPath);
+ return;
+ }
+
+ boolean mainChannel =
+ getMainChannel(device, config) == null ? false : true;
+ if (!mainChannel) {
+ String mainChannelPath =
+ mBluetoothService.getMainChannelNative(devicePath);
+ if (mainChannelPath == null) {
+ errorLog("Main Channel Path is null for devicePath:" + devicePath);
+ return;
+ }
+ if (mainChannelPath.equals(channelPath)) mainChannel = true;
+ }
+
+ channel = findConnectingChannel(device, config);
+ if (channel != null) {
+ channel.mChannelFd = fd;
+ channel.mMainChannel = mainChannel;
+ channel.mChannelPath = channelPath;
+ prevState = channel.mState;
+ } else {
+ channel = new HealthChannel(device, config, fd, mainChannel,
+ channelPath);
+ mHealthChannels.add(channel);
+ prevState = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ }
+ state = BluetoothHealth.STATE_CHANNEL_CONNECTED;
+ } else {
+ channel = findChannelByPath(device, config, channelPath);
+ if (channel == null) {
+ errorLog("Channel not found:" + config + ":" + channelPath);
+ return;
+ }
+
+ fd = channel.mChannelFd;
+ // CLOSE FD
+ mBluetoothService.releaseChannelFdNative(channel.mChannelPath);
+ mHealthChannels.remove(channel);
+
+ prevState = channel.mState;
+ state = BluetoothHealth.STATE_CHANNEL_DISCONNECTED;
+ }
+ channel.mState = state;
+ callHealthChannelCallback(config, device, prevState, state, fd);
+ }
+ }
+
+ private void callHealthChannelCallback(BluetoothHealthAppConfiguration config,
+ BluetoothDevice device, int prevState, int state, ParcelFileDescriptor fd) {
+ broadcastHealthDeviceStateChange(device, prevState, state);
+
+ debugLog("Health Device Callback: " + device + " State Change: "
+ + prevState + "->" + state);
+ try {
+ config.getCallback().onHealthChannelStateChange(config, device, prevState,
+ state, fd);
+ } catch (RemoteException e) {
+ errorLog("Error while making health channel state change callback: " + e);
+ }
+ }
+
+ private void callHealthApplicationStatusCallback(
+ BluetoothHealthAppConfiguration config, int status) {
+ debugLog("Health Device Application: " + config + " State Change: status:"
+ + status);
+ try {
+ config.getCallback().onHealthAppConfigurationStatusChange(config, status);
+ } catch (RemoteException e) {
+ errorLog("Error while making health app registration state change callback: " + e);
+ }
+ }
+
+ int getHealthDeviceConnectionState(BluetoothDevice device) {
+ if (mHealthDevices.get(device) == null) {
+ return BluetoothHealth.STATE_DISCONNECTED;
+ }
+ return mHealthDevices.get(device);
+ }
+
+ List<BluetoothDevice> getConnectedHealthDevices() {
+ List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(
+ new int[] {BluetoothHealth.STATE_CONNECTED});
+ return devices;
+ }
+
+ List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(int[] states) {
+ List<BluetoothDevice> devices = lookupHealthDevicesMatchingStates(states);
+ return devices;
+ }
+
+ List<BluetoothDevice> lookupHealthDevicesMatchingStates(int[] states) {
+ List<BluetoothDevice> healthDevices = new ArrayList<BluetoothDevice>();
+
+ for (BluetoothDevice device: mHealthDevices.keySet()) {
+ int healthDeviceState = getHealthDeviceConnectionState(device);
+ for (int state : states) {
+ if (state == healthDeviceState) {
+ healthDevices.add(device);
+ break;
+ }
+ }
+ }
+ return healthDevices;
+ }
+
+ /**
+ * This function sends the intent for the updates on the connection status to the remote device.
+ * Note that multiple channels can be connected to the remote device by multiple applications.
+ * This sends an intent for the update to the device connection status and not the channel
+ * connection status. Only the following state transitions are possible:
+ *
+ * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTING}
+ * {@link BluetoothHealth#STATE_CONNECTING} to {@link BluetoothHealth#STATE_CONNECTED}
+ * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTING}
+ * {@link BluetoothHealth#STATE_DISCONNECTING} to {@link BluetoothHealth#STATE_DISCONNECTED}
+ * {@link BluetoothHealth#STATE_DISCONNECTED} to {@link BluetoothHealth#STATE_CONNECTED}
+ * {@link BluetoothHealth#STATE_CONNECTED} to {@link BluetoothHealth#STATE_DISCONNECTED}
+ * {@link BluetoothHealth#STATE_CONNECTING} to {{@link BluetoothHealth#STATE_DISCONNECTED}
+ *
+ * @param device
+ * @param prevChannelState
+ * @param newChannelState
+ * @hide
+ */
+ private void broadcastHealthDeviceStateChange(BluetoothDevice device, int prevChannelState,
+ int newChannelState) {
+ if (mHealthDevices.get(device) == null) {
+ mHealthDevices.put(device, BluetoothHealth.STATE_DISCONNECTED);
+ }
+
+ int currDeviceState = mHealthDevices.get(device);
+ int newDeviceState = convertState(newChannelState);
+
+ if (currDeviceState != newDeviceState) {
+ List<HealthChannel> chan;
+ switch (currDeviceState) {
+ case BluetoothHealth.STATE_DISCONNECTED:
+ updateAndsendIntent(device, currDeviceState, newDeviceState);
+ break;
+ case BluetoothHealth.STATE_CONNECTING:
+ // Channel got connected.
+ if (newDeviceState == BluetoothHealth.STATE_CONNECTED) {
+ updateAndsendIntent(device, currDeviceState, newDeviceState);
+ } else {
+ // Channel got disconnected
+ chan = findChannelByStates(device, new int [] {
+ BluetoothHealth.STATE_CHANNEL_CONNECTING,
+ BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+ if (chan.isEmpty()) {
+ updateAndsendIntent(device, currDeviceState, newDeviceState);
+ }
+ }
+ break;
+ case BluetoothHealth.STATE_CONNECTED:
+ // Channel got disconnected or is in disconnecting state.
+ chan = findChannelByStates(device, new int [] {
+ BluetoothHealth.STATE_CHANNEL_CONNECTING,
+ BluetoothHealth.STATE_CHANNEL_CONNECTED});
+ if (chan.isEmpty()) {
+ updateAndsendIntent(device, currDeviceState, newDeviceState);
+ }
+ case BluetoothHealth.STATE_DISCONNECTING:
+ // Channel got disconnected.
+ chan = findChannelByStates(device, new int [] {
+ BluetoothHealth.STATE_CHANNEL_CONNECTING,
+ BluetoothHealth.STATE_CHANNEL_DISCONNECTING});
+ if (chan.isEmpty()) {
+ updateAndsendIntent(device, currDeviceState, newDeviceState);
+ }
+ break;
+ }
+ }
+ }
+
+ private void updateAndsendIntent(BluetoothDevice device, int prevDeviceState,
+ int newDeviceState) {
+ mHealthDevices.put(device, newDeviceState);
+ mBluetoothService.sendConnectionStateChange(device, prevDeviceState, newDeviceState);
+ }
+
+ /**
+ * This function converts the channel connection state to device connection state.
+ *
+ * @param state
+ * @return
+ */
+ private int convertState(int state) {
+ switch (state) {
+ case BluetoothHealth.STATE_CHANNEL_CONNECTED:
+ return BluetoothHealth.STATE_CONNECTED;
+ case BluetoothHealth.STATE_CHANNEL_CONNECTING:
+ return BluetoothHealth.STATE_CONNECTING;
+ case BluetoothHealth.STATE_CHANNEL_DISCONNECTING:
+ return BluetoothHealth.STATE_DISCONNECTING;
+ case BluetoothHealth.STATE_CHANNEL_DISCONNECTED:
+ return BluetoothHealth.STATE_DISCONNECTED;
+ }
+ errorLog("Mismatch in Channel and Health Device State");
+ return -1;
+ }
+
+ private static void debugLog(String msg) {
+ if (DBG) Log.d(TAG, msg);
+ }
+
+ private static void errorLog(String msg) {
+ Log.e(TAG, msg);
+ }
+}
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index a4588ae..7f47ebc 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -31,6 +31,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDeviceProfileState;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHealthAppConfiguration;
import android.bluetooth.BluetoothInputDevice;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -49,6 +50,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
+import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -148,6 +150,7 @@
private int mAdapterConnectionState = BluetoothAdapter.STATE_DISCONNECTED;
private BluetoothPanProfileHandler mBluetoothPanProfileHandler;
private BluetoothInputProfileHandler mBluetoothInputProfileHandler;
+ private BluetoothHealthProfileHandler mBluetoothHealthProfileHandler;
private static class RemoteService {
public String address;
@@ -220,6 +223,7 @@
mContext.registerReceiver(mReceiver, filter);
mBluetoothInputProfileHandler = BluetoothInputProfileHandler.getInstance(mContext, this);
mBluetoothPanProfileHandler = BluetoothPanProfileHandler.getInstance(mContext, this);
+ mBluetoothHealthProfileHandler = BluetoothHealthProfileHandler.getInstance(mContext, this);
}
public static synchronized String readDockBluetoothAddress() {
@@ -2077,6 +2081,106 @@
}
}
+ /**** Handlers for Health Device Profile ****/
+ // TODO: All these need to be converted to a state machine.
+
+ public boolean registerAppConfiguration(BluetoothHealthAppConfiguration config) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.registerAppConfiguration(config);
+ }
+ }
+
+ public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.unregisterAppConfiguration(config);
+ }
+ }
+
+
+ public boolean connectChannelToSource(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.connectChannelToSource(device,
+ config);
+ }
+ }
+
+ public boolean connectChannelToSink(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, int channelType) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.connectChannel(device, config,
+ channelType);
+ }
+ }
+
+ public boolean disconnectChannel(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config, ParcelFileDescriptor fd) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.disconnectChannel(device, config, fd);
+ }
+ }
+
+ public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+ BluetoothHealthAppConfiguration config) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.getMainChannelFd(device, config);
+ }
+ }
+
+ /*package*/ void onHealthDevicePropertyChanged(String devicePath,
+ String channelPath) {
+ synchronized (mBluetoothHealthProfileHandler) {
+ mBluetoothHealthProfileHandler.onHealthDevicePropertyChanged(devicePath,
+ channelPath);
+ }
+ }
+
+ /*package*/ void onHealthDeviceChannelChanged(String devicePath,
+ String channelPath, boolean exists) {
+ synchronized(mBluetoothHealthProfileHandler) {
+ mBluetoothHealthProfileHandler.onHealthDeviceChannelChanged(devicePath,
+ channelPath, exists);
+ }
+ }
+
+ public int getHealthDeviceConnectionState(BluetoothDevice device) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.getHealthDeviceConnectionState(device);
+ }
+ }
+
+ public List<BluetoothDevice> getConnectedHealthDevices() {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.getConnectedHealthDevices();
+ }
+ }
+
+ public List<BluetoothDevice> getHealthDevicesMatchingConnectionStates(
+ int[] states) {
+ mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+ "Need BLUETOOTH permission");
+ synchronized (mBluetoothHealthProfileHandler) {
+ return mBluetoothHealthProfileHandler.
+ getHealthDevicesMatchingConnectionStates(states);
+ }
+ }
+
public boolean connectHeadset(String address) {
if (getBondState(address) != BluetoothDevice.BOND_BONDED) return false;
@@ -2375,4 +2479,16 @@
private native int[] addReservedServiceRecordsNative(int[] uuuids);
private native boolean removeReservedServiceRecordsNative(int[] handles);
+
+ // Health API
+ native String registerHealthApplicationNative(int dataType, String role, String name,
+ String channelType);
+ native String registerHealthApplicationNative(int dataType, String role, String name);
+ native boolean unregisterHealthApplicationNative(String path);
+ native boolean createChannelNative(String devicePath, String appPath, String channelType);
+ native boolean destroyChannelNative(String devicePath, String channelpath);
+ native String getMainChannelNative(String path);
+ native String getChannelApplicationNative(String channelPath);
+ native ParcelFileDescriptor getChannelFdNative(String channelPath);
+ native boolean releaseChannelFdNative(String channelPath);
}
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index 69dfc2b..a491a0b 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -42,9 +42,15 @@
return mLayer;
}
+ @Override
boolean copyInto(Bitmap bitmap) {
return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
- }
+ }
+
+ @Override
+ void update(int width, int height, boolean isOpaque) {
+ super.update(width, height, isOpaque);
+ }
@Override
void destroy() {
diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java
index 3ac4459..391d9f4 100644
--- a/core/java/android/view/GLES20TextureLayer.java
+++ b/core/java/android/view/GLES20TextureLayer.java
@@ -65,12 +65,14 @@
SurfaceTexture getSurfaceTexture() {
if (mSurface == null) {
- mSurface = new SurfaceTexture(mTexture);
+ mSurface = new SurfaceTexture(mTexture, false);
}
return mSurface;
}
+ @Override
void update(int width, int height, boolean isOpaque) {
+ super.update(width, height, isOpaque);
GLES20Canvas.nUpdateTextureLayer(mLayer, width, height, isOpaque, mSurface);
}
}
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 86dec3f..dfb39ae 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -16,6 +16,7 @@
package android.view;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
/**
@@ -34,7 +35,7 @@
int mWidth;
int mHeight;
- final boolean mOpaque;
+ boolean mOpaque;
/**
* Creates a new hardware layer with undefined dimensions.
@@ -92,7 +93,7 @@
abstract boolean isValid();
/**
- * Resizes the layer, if necessary, to be at least as large
+ * Resize the layer, if necessary, to be at least as large
* as the supplied dimensions.
*
* @param width The new desired minimum width for this layer
@@ -124,4 +125,29 @@
* @param currentCanvas
*/
abstract void end(Canvas currentCanvas);
+
+ /**
+ * Copies this layer into the specified bitmap.
+ *
+ * @param bitmap The bitmap to copy they layer into
+ *
+ * @return True if the copy was successful, false otherwise
+ */
+ abstract boolean copyInto(Bitmap bitmap);
+
+ /**
+ * Update the layer's properties. This method should be used
+ * when the underlying storage is modified by an external entity.
+ * To change the underlying storage, use the {@link #resize(int, int)}
+ * method instead.
+ *
+ * @param width The new width of this layer
+ * @param height The new height of this layer
+ * @param isOpaque Whether this layer is opaque
+ */
+ void update(int width, int height, boolean isOpaque) {
+ mWidth = width;
+ mHeight = height;
+ mOpaque = isOpaque;
+ }
}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index c6d011e..bbfb4c1 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -17,12 +17,11 @@
package android.view;
-import android.graphics.Bitmap;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
-import android.os.*;
-import android.util.EventLog;
+import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
@@ -199,26 +198,6 @@
abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer);
/**
- * Updates the specified layer.
- *
- * @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, boolean isOpaque);
-
- /**
- * Copies the content of the specified layer into the specified bitmap.
- *
- * @param layer The hardware layer to copy
- * @param bitmap The bitmap to copy the layer into
- *
- * @return True if the copy was successful, false otherwise
- */
- abstract boolean copyLayer(HardwareLayer layer, Bitmap bitmap);
-
- /**
* Initializes the hardware renderer for the specified surface and setup the
* renderer for drawing, if needed. This is invoked when the ViewAncestor has
* potentially lost the hardware renderer. The hardware renderer should be
@@ -342,6 +321,13 @@
}
/**
+ * Indicates whether this renderer instance can track and update dirty regions.
+ */
+ boolean hasDirtyRegions() {
+ return mDirtyRegions;
+ }
+
+ /**
* Return a string for the EGL error code, or the hex representation
* if the error is unknown.
*
@@ -634,19 +620,14 @@
void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
Rect dirty) {
if (canDraw()) {
- if (!mDirtyRegions) {
+ if (!hasDirtyRegions()) {
dirty = null;
}
-
- attachInfo.mDrawingTime = SystemClock.uptimeMillis();
attachInfo.mIgnoreDirtyState = true;
+ attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+
view.mPrivateFlags |= View.DRAWN;
- long startTime;
- if (ViewDebug.DEBUG_PROFILE_DRAWING) {
- startTime = SystemClock.elapsedRealtime();
- }
-
final int surfaceState = checkCurrent();
if (surfaceState != SURFACE_STATE_ERROR) {
// We had to change the current surface and/or context, redraw everything
@@ -700,26 +681,9 @@
onPostDraw();
- if (ViewDebug.DEBUG_PROFILE_DRAWING) {
- EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime);
- }
-
attachInfo.mIgnoreDirtyState = false;
- final long swapBuffersStartTime;
- if (ViewDebug.DEBUG_LATENCY) {
- swapBuffersStartTime = System.nanoTime();
- }
-
sEgl.eglSwapBuffers(sEglDisplay, mEglSurface);
-
- if (ViewDebug.DEBUG_LATENCY) {
- long now = System.nanoTime();
- Log.d(LOG_TAG, "Latency: Spent "
- + ((now - swapBuffersStartTime) * 0.000001f)
- + "ms waiting for eglSwapBuffers()");
- }
-
checkEglErrors();
}
}
@@ -820,16 +784,6 @@
return ((GLES20TextureLayer) layer).getSurfaceTexture();
}
- @Override
- void updateTextureLayer(HardwareLayer layer, int width, int height, boolean isOpaque) {
- ((GLES20TextureLayer) layer).update(width, height, isOpaque);
- }
-
- @Override
- boolean copyLayer(HardwareLayer layer, Bitmap bitmap) {
- return ((GLES20Layer) layer).copyInto(bitmap);
- }
-
static HardwareRenderer create(boolean translucent) {
if (GLES20Canvas.isAvailable()) {
return new Gl20Renderer(translucent);
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index de398eb..d656f31 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -78,7 +78,7 @@
* }
*
* public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- * // Ignored
+ * // Invoked every time there's a new Camera preview frame
* }
* }
* </pre>
@@ -102,12 +102,9 @@
private boolean mOpaque = true;
- private final Runnable mUpdateLayerAction = new Runnable() {
- @Override
- public void run() {
- updateLayer();
- }
- };
+ private final Object[] mLock = new Object[0];
+ private boolean mUpdateLayer;
+
private SurfaceTexture.OnFrameAvailableListener mUpdateListener;
/**
@@ -170,7 +167,7 @@
public void setOpaque(boolean opaque) {
if (opaque != mOpaque) {
mOpaque = opaque;
- if (mLayer != null) updateLayer();
+ updateLayer();
}
}
@@ -188,7 +185,7 @@
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (isHardwareAccelerated() && mLayer != null) {
+ if (mLayer != null) {
if (mListener != null) {
mListener.onSurfaceTextureDestroyed(mSurface);
}
@@ -243,6 +240,7 @@
*/
@Override
public final void draw(Canvas canvas) {
+ applyUpdate();
}
/**
@@ -268,11 +266,11 @@
@Override
HardwareLayer getHardwareLayer() {
- if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
- return null;
- }
-
if (mLayer == null) {
+ if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+ return null;
+ }
+
mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque);
mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer);
nSetDefaultBufferSize(mSurface, getWidth(), getHeight());
@@ -282,7 +280,10 @@
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// Per SurfaceTexture's documentation, the callback may be invoked
// from an arbitrary thread
- post(mUpdateLayerAction);
+ synchronized (mLock) {
+ mUpdateLayer = true;
+ }
+ postInvalidateDelayed(0);
}
};
mSurface.setOnFrameAvailableListener(mUpdateListener);
@@ -292,6 +293,8 @@
}
}
+ applyUpdate();
+
return mLayer;
}
@@ -313,17 +316,28 @@
}
private void updateLayer() {
- if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+ mUpdateLayer = true;
+ invalidate();
+ }
+
+ private void applyUpdate() {
+ if (mLayer == null) {
return;
}
- mAttachInfo.mHardwareRenderer.updateTextureLayer(mLayer, getWidth(), getHeight(), mOpaque);
+ synchronized (mLock) {
+ if (mUpdateLayer) {
+ mUpdateLayer = false;
+ } else {
+ return;
+ }
+ }
+
+ mLayer.update(getWidth(), getHeight(), mOpaque);
if (mListener != null) {
mListener.onSurfaceTextureUpdated(mSurface);
}
-
- invalidate();
}
/**
@@ -402,7 +416,7 @@
*/
public Bitmap getBitmap(Bitmap bitmap) {
if (bitmap != null && isAvailable()) {
- mAttachInfo.mHardwareRenderer.copyLayer(mLayer, bitmap);
+ mLayer.copyInto(bitmap);
}
return bitmap;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8184643..7e75c4e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4345,19 +4345,25 @@
}
/**
- * Set the layout direction for this view.
+ * Set the layout direction for this view. This will propagate a reset of layout direction
+ * resolution to the view's children and resolve layout direction for this view.
*
* @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
* {@link #LAYOUT_DIRECTION_RTL},
* {@link #LAYOUT_DIRECTION_INHERIT} or
* {@link #LAYOUT_DIRECTION_LOCALE}.
+ *
* @attr ref android.R.styleable#View_layoutDirection
*
* @hide
*/
@RemotableViewMethod
public void setLayoutDirection(int layoutDirection) {
- setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+ if (getLayoutDirection() != layoutDirection) {
+ resetLayoutDirectionResolution();
+ // Setting the flag will also request a layout.
+ setFlags(layoutDirection, LAYOUT_DIRECTION_MASK);
+ }
}
/**
@@ -5121,12 +5127,7 @@
mInputEventConsistencyVerifier.onTrackballEvent(event, 0);
}
- //Log.i("view", "view=" + this + ", " + event.toString());
- if (onTrackballEvent(event)) {
- return true;
- }
-
- return false;
+ return onTrackballEvent(event);
}
/**
@@ -5221,6 +5222,7 @@
break;
}
+ //noinspection SimplifiableIfStatement
if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& mOnHoverListener.onHover(this, event)) {
return true;
@@ -5893,6 +5895,7 @@
*/
private boolean isHoverable() {
final int viewFlags = mViewFlags;
+ //noinspection SimplifiableIfStatement
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return false;
}
@@ -8988,7 +8991,8 @@
/**
* Reset the resolved layout direction by clearing the corresponding flag
*/
- private void resetLayoutDirectionResolution() {
+ void resetLayoutDirectionResolution() {
+ // Reset the current View resolution
mPrivateFlags2 &= ~LAYOUT_DIRECTION_RESOLVED;
}
@@ -11826,6 +11830,10 @@
mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED;
+ if (mLayoutParams != null) {
+ mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
+ }
+
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
@@ -12847,7 +12855,7 @@
* A Property wrapper around the <code>alpha</code> functionality handled by the
* {@link View#setAlpha(float)} and {@link View#getAlpha()} methods.
*/
- static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+ public static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
@Override
public void setValue(View object, float value) {
object.setAlpha(value);
@@ -13573,6 +13581,12 @@
boolean mIgnoreDirtyState;
/**
+ * This flag tracks when the mIgnoreDirtyState flag is set during draw(),
+ * to avoid clearing that flag prematurely.
+ */
+ boolean mSetIgnoreDirtyState = false;
+
+ /**
* Indicates whether the view's window is currently in touch mode.
*/
boolean mInTouchMode;
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index ad660c1..e5d6d9f 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -288,10 +288,6 @@
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.
*/
@@ -676,7 +672,7 @@
}
}
if (!mDirty.isEmpty() && !mDirty.contains(dirty)) {
- mSetIgnoreDirtyState = true;
+ mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
mDirty.union(dirty);
@@ -1882,10 +1878,10 @@
}
canvas.setScreenDensity(scalingRequired
? DisplayMetrics.DENSITY_DEVICE : 0);
- mSetIgnoreDirtyState = false;
+ mAttachInfo.mSetIgnoreDirtyState = false;
mView.draw(canvas);
} finally {
- if (!mSetIgnoreDirtyState) {
+ if (!mAttachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
mAttachInfo.mIgnoreDirtyState = false;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index b9474e6..6405398 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4998,6 +4998,24 @@
}
/**
+ * This method will be called when we need to reset the layout direction resolution flag
+ *
+ */
+ @Override
+ void resetLayoutDirectionResolution() {
+ super.resetLayoutDirectionResolution();
+
+ // Take care of resetting the children resolution too
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child.getLayoutDirection() == LAYOUT_DIRECTION_INHERIT) {
+ child.resetLayoutDirectionResolution();
+ }
+ }
+ }
+
+ /**
* Return true if the pressed state should be delayed for children or descendants of this
* ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
* This prevents the pressed state from appearing when the user is actually trying to scroll
@@ -5157,6 +5175,21 @@
}
/**
+ * Resolve layout parameters depending on the layout direction. Subclasses that care about
+ * layoutDirection changes should override this method. The default implementation does
+ * nothing.
+ *
+ * @param layoutDirection the direction of the layout
+ *
+ * {@link View#LAYOUT_DIRECTION_LTR}
+ * {@link View#LAYOUT_DIRECTION_RTL}
+ *
+ * @hide
+ */
+ protected void resolveWithDirection(int layoutDirection) {
+ }
+
+ /**
* Returns a String representation of this set of layout parameters.
*
* @param output the String to prepend to the internal representation
@@ -5197,30 +5230,56 @@
*/
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
/**
- * The left margin in pixels of the child.
+ * The left margin in pixels of the child. Whenever this value is changed, a call to
+ * {@link android.view.View#requestLayout()} needs to be done.
*/
@ViewDebug.ExportedProperty(category = "layout")
public int leftMargin;
/**
- * The top margin in pixels of the child.
+ * The top margin in pixels of the child. Whenever this value is changed, a call to
+ * {@link android.view.View#requestLayout()} needs to be done.
*/
@ViewDebug.ExportedProperty(category = "layout")
public int topMargin;
/**
- * The right margin in pixels of the child.
+ * The right margin in pixels of the child. Whenever this value is changed, a call to
+ * {@link android.view.View#requestLayout()} needs to be done.
*/
@ViewDebug.ExportedProperty(category = "layout")
public int rightMargin;
/**
- * The bottom margin in pixels of the child.
+ * The bottom margin in pixels of the child. Whenever this value is changed, a call to
+ * {@link android.view.View#requestLayout()} needs to be done.
*/
@ViewDebug.ExportedProperty(category = "layout")
public int bottomMargin;
/**
+ * The start margin in pixels of the child.
+ *
+ * @hide
+ *
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int startMargin = DEFAULT_RELATIVE;
+
+ /**
+ * The end margin in pixels of the child.
+ *
+ * @hide
+ */
+ @ViewDebug.ExportedProperty(category = "layout")
+ protected int endMargin = DEFAULT_RELATIVE;
+
+ /**
+ * The default start and end margin.
+ */
+ static private final int DEFAULT_RELATIVE = Integer.MIN_VALUE;
+
+ /**
* Creates a new set of layout parameters. The values are extracted from
* the supplied attributes set and context.
*
@@ -5252,6 +5311,10 @@
R.styleable.ViewGroup_MarginLayout_layout_marginRight, 0);
bottomMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginBottom, 0);
+ startMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginStart, DEFAULT_RELATIVE);
+ endMargin = a.getDimensionPixelSize(
+ R.styleable.ViewGroup_MarginLayout_layout_marginEnd, DEFAULT_RELATIVE);
}
a.recycle();
@@ -5277,6 +5340,8 @@
this.topMargin = source.topMargin;
this.rightMargin = source.rightMargin;
this.bottomMargin = source.bottomMargin;
+ this.startMargin = source.startMargin;
+ this.endMargin = source.endMargin;
}
/**
@@ -5287,7 +5352,9 @@
}
/**
- * Sets the margins, in pixels.
+ * Sets the margins, in pixels. A call to {@link android.view.View#requestLayout()} needs
+ * to be done so that the new margins are taken into account. Left and right margins may be
+ * overriden by {@link android.view.View#requestLayout()} depending on layout direction.
*
* @param left the left margin size
* @param top the top margin size
@@ -5305,6 +5372,92 @@
rightMargin = right;
bottomMargin = bottom;
}
+
+ /**
+ * Sets the relative margins, in pixels. A call to {@link android.view.View#requestLayout()}
+ * needs to be done so that the new relative margins are taken into account. Left and right
+ * margins may be overriden by {@link android.view.View#requestLayout()} depending on layout
+ * direction.
+ *
+ * @param start the start margin size
+ * @param top the top margin size
+ * @param end the right margin size
+ * @param bottom the bottom margin size
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
+ *
+ * @hide
+ */
+ public void setMarginsRelative(int start, int top, int end, int bottom) {
+ startMargin = start;
+ topMargin = top;
+ endMargin = end;
+ bottomMargin = bottom;
+ }
+
+ /**
+ * Returns the start margin in pixels.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ *
+ * @return the start margin in pixels.
+ *
+ * @hide
+ */
+ public int getMarginStart() {
+ return startMargin;
+ }
+
+ /**
+ * Returns the end margin in pixels.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ *
+ * @return the end margin in pixels.
+ *
+ * @hide
+ */
+ public int getMarginEnd() {
+ return endMargin;
+ }
+
+ /**
+ * Check if margins are relative.
+ *
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+ * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+ *
+ * @return true if either marginStart or marginEnd has been set
+ *
+ * @hide
+ */
+ public boolean isMarginRelative() {
+ return (startMargin != DEFAULT_RELATIVE) || (endMargin != DEFAULT_RELATIVE);
+ }
+
+ /**
+ * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
+ * maybe overriden depending on layout direction.
+ *
+ * @hide
+ */
+ @Override
+ protected void resolveWithDirection(int layoutDirection) {
+ switch(layoutDirection) {
+ case View.LAYOUT_DIRECTION_RTL:
+ leftMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : leftMargin;
+ rightMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : rightMargin;
+ break;
+ case View.LAYOUT_DIRECTION_LTR:
+ default:
+ leftMargin = (startMargin > DEFAULT_RELATIVE) ? startMargin : leftMargin;
+ rightMargin = (endMargin > DEFAULT_RELATIVE) ? endMargin : rightMargin;
+ break;
+ }
+ }
}
/* Describes a touched view and the ids of the pointers that it has captured.
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 3a09e3b..7e41d36 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2000,8 +2000,12 @@
if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw start");
draw.mBaseLayer = nativeRecordContent(draw.mInvalRegion, draw.mContentSize);
if (draw.mBaseLayer == 0) {
- if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
- mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+ if (mWebView != null && !mWebView.isPaused()) {
+ if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, resending draw message");
+ mEventHub.sendMessage(Message.obtain(null, EventHub.WEBKIT_DRAW));
+ } else {
+ if (DebugFlags.WEB_VIEW_CORE) Log.v(LOGTAG, "webkitDraw abort, webview paused");
+ }
return;
}
webkitDraw(draw);
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 77df7c8..17aea8b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7668,7 +7668,7 @@
}
String searchedLowerCase = searched.toString().toLowerCase();
String thisTextLowerCase = thisText.toString().toLowerCase();
- if (thisTextLowerCase.contains(searched)) {
+ if (thisTextLowerCase.contains(searchedLowerCase)) {
outViews.add(this);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 6204b4e..8eb046e 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -50,7 +50,6 @@
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
-import android.view.View.MeasureSpec;
import android.widget.AdapterView;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -649,7 +648,7 @@
}
}
}
-
+
private void initTitle() {
if (mTitleLayout == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
@@ -989,8 +988,8 @@
int ypos = 0;
switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
case Gravity.CENTER_VERTICAL:
- final int paddedTop = mTop + getPaddingTop();
- final int paddedBottom = mBottom - getPaddingBottom();
+ final int paddedTop = getPaddingTop();
+ final int paddedBottom = mBottom - mTop - getPaddingBottom();
ypos = ((paddedBottom - paddedTop) - customView.getMeasuredHeight()) / 2;
break;
case Gravity.TOP:
@@ -1001,7 +1000,10 @@
- bottomMargin;
break;
}
- x += positionChild(customView, xpos, ypos, contentHeight);
+ final int customWidth = customView.getMeasuredWidth();
+ customView.layout(xpos, ypos, xpos + customWidth,
+ ypos + customView.getMeasuredHeight());
+ x += customWidth;
}
if (mProgressView != null) {
@@ -1162,13 +1164,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;
}
@@ -1176,8 +1179,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/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..dd8b378 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -91,7 +91,8 @@
virtual void onFrameAvailable();
private:
- static JNIEnv* getJNIEnv();
+ static JNIEnv* getJNIEnv(bool* needsDetach);
+ static void detachJNI();
jobject mWeakThiz;
jclass mClazz;
@@ -103,37 +104,57 @@
mClazz((jclass)env->NewGlobalRef(clazz))
{}
-JNIEnv* JNISurfaceTextureContext::getJNIEnv() {
- JNIEnv* env;
- JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
- JavaVM* vm = AndroidRuntime::getJavaVM();
- int result = vm->AttachCurrentThread(&env, (void*) &args);
- if (result != JNI_OK) {
- LOGE("thread attach failed: %#x", result);
- return NULL;
+JNIEnv* JNISurfaceTextureContext::getJNIEnv(bool* needsDetach) {
+ *needsDetach = false;
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ if (env == NULL) {
+ JavaVMAttachArgs args = {JNI_VERSION_1_4, NULL, NULL};
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ int result = vm->AttachCurrentThread(&env, (void*) &args);
+ if (result != JNI_OK) {
+ LOGE("thread attach failed: %#x", result);
+ return NULL;
+ }
+ *needsDetach = true;
}
return env;
}
+void JNISurfaceTextureContext::detachJNI() {
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ int result = vm->DetachCurrentThread();
+ if (result != JNI_OK) {
+ LOGE("thread detach failed: %#x", result);
+ }
+}
+
JNISurfaceTextureContext::~JNISurfaceTextureContext()
{
- JNIEnv* env = getJNIEnv();
+ bool needsDetach = false;
+ JNIEnv* env = getJNIEnv(&needsDetach);
if (env != NULL) {
env->DeleteGlobalRef(mWeakThiz);
env->DeleteGlobalRef(mClazz);
} else {
LOGW("leaking JNI object references");
}
+ if (needsDetach) {
+ detachJNI();
+ }
}
void JNISurfaceTextureContext::onFrameAvailable()
{
- JNIEnv *env = getJNIEnv();
+ bool needsDetach = false;
+ JNIEnv* env = getJNIEnv(&needsDetach);
if (env != NULL) {
env->CallStaticVoidMethod(mClazz, fields.postEvent, mWeakThiz);
} else {
LOGW("onFrameAvailable event will not posted");
}
+ if (needsDetach) {
+ detachJNI();
+ }
}
// ----------------------------------------------------------------------------
@@ -155,9 +176,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 +233,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/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index a5b2006..6a13876 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -461,12 +461,30 @@
Vector<GlyphRun> glyphRuns;
jchar* runGlyphs;
size_t runGlyphsCount = 0;
- size_t runIndex = 0;
+ int32_t end = start + count;
for (size_t i = 0; i < rc; ++i) {
int32_t startRun;
int32_t lengthRun;
UBiDiDirection runDir = ubidi_getVisualRun(bidi, i, &startRun, &lengthRun);
+ if (startRun >= end) {
+ break;
+ }
+
+ int32_t endRun = startRun + lengthRun;
+ if (endRun <= start) {
+ continue;
+ }
+
+ if (startRun < start) {
+ startRun = start;
+ }
+ if (endRun > end) {
+ endRun = end;
+ }
+
+ lengthRun = endRun - startRun;
+
int newFlags = (runDir == UBIDI_RTL) ? kDirection_RTL : kDirection_LTR;
jfloat runTotalAdvance = 0;
#if DEBUG_GLYPHS
@@ -475,11 +493,10 @@
#endif
computeRunValuesWithHarfbuzz(paint, chars, startRun,
lengthRun, contextCount, newFlags,
- outAdvances + runIndex, &runTotalAdvance,
+ outAdvances, &runTotalAdvance,
&runGlyphs, &runGlyphsCount);
- runIndex += lengthRun;
-
+ outAdvances += lengthRun;
*outTotalAdvance += runTotalAdvance;
*outGlyphsCount += runGlyphsCount;
#if DEBUG_GLYPHS
diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp
index 6ae3e35..5c6f5e8 100644
--- a/core/jni/android_bluetooth_common.cpp
+++ b/core/jni/android_bluetooth_common.cpp
@@ -81,6 +81,16 @@
{"UUID", DBUS_TYPE_STRING},
};
+static Properties health_device_properties[] = {
+ {"MainChannel", DBUS_TYPE_OBJECT_PATH},
+};
+
+static Properties health_channel_properties[] = {
+ {"Type", DBUS_TYPE_STRING},
+ {"Device", DBUS_TYPE_OBJECT_PATH},
+ {"Application", DBUS_TYPE_OBJECT_PATH},
+};
+
typedef union {
char *str_val;
int int_val;
@@ -315,6 +325,22 @@
return ret;
}
+jint dbus_returns_unixfd(JNIEnv *env, DBusMessage *reply) {
+
+ DBusError err;
+ jint ret = -1;
+
+ dbus_error_init(&err);
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_UNIX_FD, &ret,
+ DBUS_TYPE_INVALID)) {
+ LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, reply);
+ }
+ dbus_message_unref(reply);
+ return ret;
+}
+
+
jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply) {
DBusError err;
@@ -486,6 +512,55 @@
dbus_message_iter_close_container(iter, &value_iter);
}
+static void dict_append_entry(DBusMessageIter *dict,
+ const char *key, int type, void *val)
+{
+ DBusMessageIter dict_entry;
+ dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY,
+ NULL, &dict_entry);
+
+ dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
+ append_variant(&dict_entry, type, val);
+ dbus_message_iter_close_container(dict, &dict_entry);
+}
+
+static void append_dict_valist(DBusMessageIter *iterator, const char *first_key,
+ va_list var_args)
+{
+ DBusMessageIter dict;
+ int val_type;
+ const char *val_key;
+ void *val;
+
+ dbus_message_iter_open_container(iterator, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ val_key = first_key;
+ while (val_key) {
+ val_type = va_arg(var_args, int);
+ val = va_arg(var_args, void *);
+ dict_append_entry(&dict, val_key, val_type, val);
+ val_key = va_arg(var_args, char *);
+ }
+
+ dbus_message_iter_close_container(iterator, &dict);
+}
+
+void append_dict_args(DBusMessage *reply, const char *first_key, ...)
+{
+ DBusMessageIter iter;
+ va_list var_args;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ va_start(var_args, first_key);
+ append_dict_valist(&iter, first_key, var_args);
+ va_end(var_args);
+}
+
+
int get_property(DBusMessageIter iter, Properties *properties,
int max_num_properties, int *prop_index, property_value *value, int *len) {
DBusMessageIter prop_val, array_val_iter;
@@ -737,6 +812,21 @@
sizeof(input_properties) / sizeof(Properties));
}
+jobjectArray parse_health_device_properties(JNIEnv *env, DBusMessageIter *iter) {
+ return parse_properties(env, iter, (Properties *) &health_device_properties,
+ sizeof(health_device_properties) / sizeof(Properties));
+}
+
+jobjectArray parse_health_device_property_change(JNIEnv *env, DBusMessage *msg) {
+ return parse_property_change(env, msg, (Properties *) &health_device_properties,
+ sizeof(health_device_properties) / sizeof(Properties));
+}
+
+jobjectArray parse_health_channel_properties(JNIEnv *env, DBusMessageIter *iter) {
+ return parse_properties(env, iter, (Properties *) &health_channel_properties,
+ sizeof(health_channel_properties) / sizeof(Properties));
+}
+
int get_bdaddr(const char *str, bdaddr_t *ba) {
char *d = ((char *)ba) + 5, *endp;
int i;
diff --git a/core/jni/android_bluetooth_common.h b/core/jni/android_bluetooth_common.h
index 3364703..2f5fd5a 100644
--- a/core/jni/android_bluetooth_common.h
+++ b/core/jni/android_bluetooth_common.h
@@ -149,6 +149,7 @@
jint dbus_returns_int32(JNIEnv *env, DBusMessage *reply);
jint dbus_returns_uint32(JNIEnv *env, DBusMessage *reply);
+jint dbus_returns_unixfd(JNIEnv *env, DBusMessage *reply);
jstring dbus_returns_string(JNIEnv *env, DBusMessage *reply);
jboolean dbus_returns_boolean(JNIEnv *env, DBusMessage *reply);
jobjectArray dbus_returns_array_of_strings(JNIEnv *env, DBusMessage *reply);
@@ -164,8 +165,13 @@
jobjectArray parse_remote_device_property_change(JNIEnv *env, DBusMessage *msg);
jobjectArray parse_adapter_property_change(JNIEnv *env, DBusMessage *msg);
jobjectArray parse_input_properties(JNIEnv *env, DBusMessageIter *iter);
+jobjectArray parse_health_device_properties(JNIEnv *env, DBusMessageIter *iter);
+jobjectArray parse_health_channel_properties(JNIEnv *env, DBusMessageIter *iter);
jobjectArray parse_input_property_change(JNIEnv *env, DBusMessage *msg);
jobjectArray parse_pan_property_change(JNIEnv *env, DBusMessage *msg);
+jobjectArray parse_health_device_property_change(JNIEnv *env, DBusMessage *msg);
+
+void append_dict_args(DBusMessage *reply, const char *first_key, ...);
void append_variant(DBusMessageIter *iter, int type, void *val);
int get_bdaddr(const char *str, bdaddr_t *ba);
void get_bdaddr_as_string(const bdaddr_t *ba, char *str);
diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp
index 59b97c2..fb25486 100644
--- a/core/jni/android_server_BluetoothEventLoop.cpp
+++ b/core/jni/android_server_BluetoothEventLoop.cpp
@@ -72,6 +72,8 @@
static jmethodID method_onInputDeviceConnectionResult;
static jmethodID method_onPanDevicePropertyChanged;
static jmethodID method_onPanDeviceConnectionResult;
+static jmethodID method_onHealthDevicePropertyChanged;
+static jmethodID method_onHealthDeviceChannelChanged;
typedef event_loop_native_data_t native_data_t;
@@ -139,6 +141,10 @@
"(Ljava/lang/String;[Ljava/lang/String;)V");
method_onPanDeviceConnectionResult = env->GetMethodID(clazz, "onPanDeviceConnectionResult",
"(Ljava/lang/String;I)V");
+ method_onHealthDevicePropertyChanged = env->GetMethodID(clazz, "onHealthDevicePropertyChanged",
+ "(Ljava/lang/String;[Ljava/lang/String;)V");
+ method_onHealthDeviceChannelChanged = env->GetMethodID(clazz, "onHealthDeviceChannelChanged",
+ "(Ljava/lang/String;Ljava/lang/String;Z)V");
method_onRequestOobData = env->GetMethodID(clazz, "onRequestOobData",
"(Ljava/lang/String;I)V");
@@ -271,6 +277,15 @@
LOG_AND_FREE_DBUS_ERROR(&err);
return JNI_FALSE;
}
+
+ dbus_bus_add_match(nat->conn,
+ "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'",
+ &err);
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ return JNI_FALSE;
+ }
+
dbus_bus_add_match(nat->conn,
"type='signal',interface='org.bluez.AudioSink'",
&err);
@@ -459,6 +474,12 @@
LOG_AND_FREE_DBUS_ERROR(&err);
}
dbus_bus_remove_match(nat->conn,
+ "type='signal',interface='"BLUEZ_DBUS_BASE_IFC".HealthDevice'",
+ &err);
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ dbus_bus_remove_match(nat->conn,
"type='signal',interface='org.bluez.audio.Manager'",
&err);
if (dbus_error_is_set(&err)) {
@@ -985,6 +1006,58 @@
LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
}
goto success;
+ } else if (dbus_message_is_signal(msg,
+ "org.bluez.HealthDevice",
+ "ChannelConnected")) {
+ const char *c_path = dbus_message_get_path(msg);
+ const char *c_channel_path;
+ jboolean exists = JNI_TRUE;
+ if (dbus_message_get_args(msg, &err,
+ DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+ DBUS_TYPE_INVALID)) {
+ env->CallVoidMethod(nat->me,
+ method_onHealthDeviceChannelChanged,
+ env->NewStringUTF(c_path),
+ env->NewStringUTF(c_channel_path),
+ exists);
+ } else {
+ LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+ }
+ goto success;
+ } else if (dbus_message_is_signal(msg,
+ "org.bluez.NetworkServer",
+ "ChannelDeleted")) {
+
+ const char *c_path = dbus_message_get_path(msg);
+ const char *c_channel_path;
+ jboolean exists = JNI_FALSE;
+ if (dbus_message_get_args(msg, &err,
+ DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+ DBUS_TYPE_INVALID)) {
+ env->CallVoidMethod(nat->me,
+ method_onHealthDeviceChannelChanged,
+ env->NewStringUTF(c_path),
+ env->NewStringUTF(c_channel_path),
+ exists);
+ } else {
+ LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+ }
+ goto success;
+ } else if (dbus_message_is_signal(msg,
+ "org.bluez.HealthDevice",
+ "PropertyChanged")) {
+ jobjectArray str_array =
+ parse_health_device_property_change(env, msg);
+ if (str_array != NULL) {
+ const char *c_path = dbus_message_get_path(msg);
+ env->CallVoidMethod(nat->me,
+ method_onHealthDevicePropertyChanged,
+ env->NewStringUTF(c_path),
+ str_array);
+ } else {
+ LOG_AND_FREE_DBUS_ERROR_WITH_MSG(&err, msg);
+ }
+ goto success;
}
ret = a2dp_event_filter(msg, env);
diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp
index ac75634..036c34c 100644
--- a/core/jni/android_server_BluetoothService.cpp
+++ b/core/jni/android_server_BluetoothService.cpp
@@ -19,12 +19,16 @@
#define DBUS_INPUT_IFACE BLUEZ_DBUS_BASE_IFC ".Input"
#define DBUS_NETWORK_IFACE BLUEZ_DBUS_BASE_IFC ".Network"
#define DBUS_NETWORKSERVER_IFACE BLUEZ_DBUS_BASE_IFC ".NetworkServer"
-
+#define DBUS_HEALTH_MANAGER_PATH "/org/bluez"
+#define DBUS_HEALTH_MANAGER_IFACE BLUEZ_DBUS_BASE_IFC ".HealthManager"
+#define DBUS_HEALTH_DEVICE_IFACE BLUEZ_DBUS_BASE_IFC ".HealthDevice"
+#define DBUS_HEALTH_CHANNEL_IFACE BLUEZ_DBUS_BASE_IFC ".HealthChannel"
#define LOG_TAG "BluetoothService.cpp"
#include "android_bluetooth_common.h"
#include "android_runtime/AndroidRuntime.h"
+#include "android_util_Binder.h"
#include "JNIHelp.h"
#include "jni.h"
#include "utils/Log.h"
@@ -1255,6 +1259,400 @@
return JNI_FALSE;
}
+static jstring registerHealthApplicationNative(JNIEnv *env, jobject object,
+ jint dataType, jstring role,
+ jstring name, jstring channelType) {
+ LOGV("%s", __FUNCTION__);
+ jstring path = NULL;
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_role = env->GetStringUTFChars(role, NULL);
+ const char *c_name = env->GetStringUTFChars(name, NULL);
+ const char *c_channel_type = env->GetStringUTFChars(channelType, NULL);
+ char *c_path;
+ DBusMessage *msg, *reply;
+ DBusError err;
+ dbus_error_init(&err);
+
+ msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
+ DBUS_HEALTH_MANAGER_PATH,
+ DBUS_HEALTH_MANAGER_IFACE,
+ "CreateApplication");
+
+ if (msg == NULL) {
+ LOGE("Could not allocate D-Bus message object!");
+ return NULL;
+ }
+
+ /* append arguments */
+ append_dict_args(msg,
+ "DataType", DBUS_TYPE_UINT16, &dataType,
+ "Role", DBUS_TYPE_STRING, &c_role,
+ "Description", DBUS_TYPE_STRING, &c_name,
+ "ChannelType", DBUS_TYPE_STRING, &c_channel_type,
+ DBUS_TYPE_INVALID);
+
+
+ /* Make the call. */
+ reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+
+ env->ReleaseStringUTFChars(role, c_role);
+ env->ReleaseStringUTFChars(name, c_name);
+ env->ReleaseStringUTFChars(channelType, c_channel_type);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_OBJECT_PATH, &c_path,
+ DBUS_TYPE_INVALID)) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ path = env->NewStringUTF(c_path);
+ }
+ dbus_message_unref(reply);
+ }
+ }
+#endif
+ return path;
+}
+
+static jstring registerSinkHealthApplicationNative(JNIEnv *env, jobject object,
+ jint dataType, jstring role,
+ jstring name) {
+ LOGV("%s", __FUNCTION__);
+ jstring path = NULL;
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_role = env->GetStringUTFChars(role, NULL);
+ const char *c_name = env->GetStringUTFChars(name, NULL);
+ char *c_path;
+
+ DBusMessage *msg, *reply;
+ DBusError err;
+ dbus_error_init(&err);
+
+ msg = dbus_message_new_method_call(BLUEZ_DBUS_BASE_IFC,
+ DBUS_HEALTH_MANAGER_PATH,
+ DBUS_HEALTH_MANAGER_IFACE,
+ "CreateApplication");
+
+ if (msg == NULL) {
+ LOGE("Could not allocate D-Bus message object!");
+ return NULL;
+ }
+
+ /* append arguments */
+ append_dict_args(msg,
+ "DataType", DBUS_TYPE_UINT16, &dataType,
+ "Role", DBUS_TYPE_STRING, &c_role,
+ "Description", DBUS_TYPE_STRING, &c_name,
+ DBUS_TYPE_INVALID);
+
+
+ /* Make the call. */
+ reply = dbus_connection_send_with_reply_and_block(nat->conn, msg, -1, &err);
+
+ env->ReleaseStringUTFChars(role, c_role);
+ env->ReleaseStringUTFChars(name, c_name);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ LOGE("--_Call made getting the patch...");
+ if (!dbus_message_get_args(reply, &err,
+ DBUS_TYPE_OBJECT_PATH, &c_path,
+ DBUS_TYPE_INVALID)) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ path = env->NewStringUTF(c_path);
+ LOGE("----Path is %s", c_path);
+ }
+ dbus_message_unref(reply);
+ }
+ }
+#endif
+ return path;
+}
+
+static jboolean unregisterHealthApplicationNative(JNIEnv *env, jobject object,
+ jstring path) {
+ LOGV("%s", __FUNCTION__);
+ jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_path = env->GetStringUTFChars(path, NULL);
+ DBusError err;
+ dbus_error_init(&err);
+ DBusMessage *reply =
+ dbus_func_args_timeout(env, nat->conn, -1,
+ DBUS_HEALTH_MANAGER_PATH,
+ DBUS_HEALTH_MANAGER_IFACE, "DestroyApplication",
+ DBUS_TYPE_OBJECT_PATH, &c_path,
+ DBUS_TYPE_INVALID);
+
+ env->ReleaseStringUTFChars(path, c_path);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ result = JNI_TRUE;
+ }
+ }
+#endif
+ return result;
+}
+
+static jboolean createChannelNative(JNIEnv *env, jobject object,
+ jstring devicePath, jstring appPath, jstring config) {
+ LOGV("%s", __FUNCTION__);
+ jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+
+ if (nat) {
+ DBusError err;
+ dbus_error_init(&err);
+
+ const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+ const char *c_app_path = env->GetStringUTFChars(appPath, NULL);
+ const char *c_config = env->GetStringUTFChars(config, NULL);
+ LOGE("Params...%s, %s, %s \n", c_device_path, c_app_path, c_config);
+
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_device_path,
+ DBUS_HEALTH_DEVICE_IFACE,
+ "CreateChannel",
+ DBUS_TYPE_OBJECT_PATH, &c_app_path,
+ DBUS_TYPE_STRING, &c_config,
+ DBUS_TYPE_INVALID);
+
+
+ env->ReleaseStringUTFChars(devicePath, c_device_path);
+ env->ReleaseStringUTFChars(appPath, c_app_path);
+ env->ReleaseStringUTFChars(config, c_config);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ result = JNI_TRUE;
+ }
+ }
+#endif
+ return result;
+}
+
+static jboolean destroyChannelNative(JNIEnv *env, jobject object, jstring devicePath,
+ jstring channelPath) {
+ LOGE("%s", __FUNCTION__);
+ jboolean result = JNI_FALSE;
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+
+ if (nat) {
+ DBusError err;
+ dbus_error_init(&err);
+
+ const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+ const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_device_path,
+ DBUS_HEALTH_DEVICE_IFACE,
+ "DestroyChannel",
+ DBUS_TYPE_OBJECT_PATH, &c_channel_path,
+ DBUS_TYPE_INVALID);
+
+ env->ReleaseStringUTFChars(devicePath, c_device_path);
+ env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ result = JNI_TRUE;
+ }
+ }
+#endif
+ return result;
+}
+
+static jstring getMainChannelNative(JNIEnv *env, jobject object, jstring devicePath) {
+ LOGE("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_device_path = env->GetStringUTFChars(devicePath, NULL);
+ DBusError err;
+ dbus_error_init(&err);
+
+ LOGE("---Args %s", c_device_path);
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_device_path,
+ DBUS_HEALTH_DEVICE_IFACE, "GetProperties",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(devicePath, c_device_path);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ DBusMessageIter iter;
+ jobjectArray str_array = NULL;
+ if (dbus_message_iter_init(reply, &iter))
+ str_array = parse_health_device_properties(env, &iter);
+ dbus_message_unref(reply);
+ jstring path = (jstring) env->GetObjectArrayElement(str_array, 1);
+
+ return path;
+ }
+ }
+#endif
+ return NULL;
+}
+
+static jstring getChannelApplicationNative(JNIEnv *env, jobject object, jstring channelPath) {
+ LOGE("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+ DBusError err;
+ dbus_error_init(&err);
+
+ LOGE("---Args %s", c_channel_path);
+
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_channel_path,
+ DBUS_HEALTH_CHANNEL_IFACE, "GetProperties",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ } else {
+ DBusMessageIter iter;
+ jobjectArray str_array = NULL;
+ if (dbus_message_iter_init(reply, &iter))
+ str_array = parse_health_channel_properties(env, &iter);
+ dbus_message_unref(reply);
+
+ jint len = env->GetArrayLength(str_array);
+
+ jstring name, path;
+ const char *c_name;
+
+ for (int i = 0; i < len; i+=2) {
+ name = (jstring) env->GetObjectArrayElement(str_array, i);
+ c_name = env->GetStringUTFChars(name, NULL);
+
+ if (!strcmp(c_name, "Application")) {
+ path = (jstring) env->GetObjectArrayElement(str_array, i+1);
+ LOGE("----Path is %s", env->GetStringUTFChars(path, NULL));
+ env->ReleaseStringUTFChars(name, c_name);
+ return path;
+ }
+ env->ReleaseStringUTFChars(name, c_name);
+ }
+ }
+ }
+#endif
+ return NULL;
+}
+
+static jboolean releaseChannelFdNative(JNIEnv *env, jobject object, jstring channelPath) {
+ LOGV("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+ DBusError err;
+ dbus_error_init(&err);
+
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_channel_path,
+ DBUS_HEALTH_CHANNEL_IFACE, "Release",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+ return reply ? JNI_TRUE : JNI_FALSE;
+ }
+#endif
+ return JNI_FALSE;
+}
+
+static jobject getChannelFdNative(JNIEnv *env, jobject object, jstring channelPath) {
+ LOGV("%s", __FUNCTION__);
+#ifdef HAVE_BLUETOOTH
+ native_data_t *nat = get_native_data(env, object);
+ if (nat) {
+ const char *c_channel_path = env->GetStringUTFChars(channelPath, NULL);
+ int32_t fd;
+ DBusError err;
+ dbus_error_init(&err);
+
+ DBusMessage *reply = dbus_func_args(env, nat->conn,
+ c_channel_path,
+ DBUS_HEALTH_CHANNEL_IFACE, "Acquire",
+ DBUS_TYPE_INVALID);
+ env->ReleaseStringUTFChars(channelPath, c_channel_path);
+
+ if (!reply) {
+ if (dbus_error_is_set(&err)) {
+ LOG_AND_FREE_DBUS_ERROR(&err);
+ }
+ return NULL;
+ }
+
+ fd = dbus_returns_unixfd(env, reply);
+ if (fd == -1) return NULL;
+
+ LOGE("---got fd %d\n", fd);
+ // Create FileDescriptor object
+ jobject fileDesc = jniCreateFileDescriptor(env, fd);
+ if (fileDesc == NULL) {
+ // FileDescriptor constructor has thrown an exception
+ releaseChannelFdNative(env, object, channelPath);
+ LOGE("---File Desc is null");
+ return NULL;
+ }
+
+ // Wrap it in a ParcelFileDescriptor
+ jobject parcelFileDesc = newParcelFileDescriptor(env, fileDesc);
+ if (parcelFileDesc == NULL) {
+ // ParcelFileDescriptor constructor has thrown an exception
+ releaseChannelFdNative(env, object, channelPath);
+ LOGE("---Parcel File Desc is null");
+ return NULL;
+ }
+
+ return parcelFileDesc;
+ }
+#endif
+ return NULL;
+}
+
+
+
static JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"classInitNative", "()V", (void*)classInitNative},
@@ -1317,6 +1715,24 @@
{"disconnectPanDeviceNative", "(Ljava/lang/String;)Z", (void *)disconnectPanDeviceNative},
{"disconnectPanServerDeviceNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
(void *)disconnectPanServerDeviceNative},
+ // Health function
+ {"registerHealthApplicationNative",
+ "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+ (void *)registerHealthApplicationNative},
+ {"registerHealthApplicationNative",
+ "(ILjava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
+ (void *)registerSinkHealthApplicationNative},
+
+ {"unregisterHealthApplicationNative", "(Ljava/lang/String;)Z",
+ (void *)unregisterHealthApplicationNative},
+ {"createChannelNative", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+ (void *)createChannelNative},
+ {"destroyChannelNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)destroyChannelNative},
+ {"getMainChannelNative", "(Ljava/lang/String;)Ljava/lang/String;", (void *)getMainChannelNative},
+ {"getChannelApplicationNative", "(Ljava/lang/String;)Ljava/lang/String;",
+ (void *)getChannelApplicationNative},
+ {"getChannelFdNative", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", (void *)getChannelFdNative},
+ {"releaseChannelFdNative", "(Ljava/lang/String;)Z", (void *)releaseChannelFdNative},
};
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index aff4394..537aae6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -649,9 +649,7 @@
float transform[16];
sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, surface));
- while (surfaceTexture->getQueuedCount() > 0) {
- surfaceTexture->updateTexImage();
- }
+ surfaceTexture->updateTexImage();
surfaceTexture->getTransformMatrix(transform);
GLenum renderTarget = surfaceTexture->getCurrentTextureTarget();
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/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-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/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/action_bar_home.xml b/core/res/res/layout/action_bar_home.xml
index 835e039c..9612710 100644
--- a/core/res/res/layout/action_bar_home.xml
+++ b/core/res/res/layout/action_bar_home.xml
@@ -30,8 +30,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="8dip"
- android:paddingTop="@dimen/action_bar_icon_vertical_padding"
- android:paddingBottom="@dimen/action_bar_icon_vertical_padding"
+ 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_password_landscape.xml b/core/res/res/layout/keyguard_screen_password_landscape.xml
index 937d5dc..8ba08f6 100644
--- a/core/res/res/layout/keyguard_screen_password_landscape.xml
+++ b/core/res/res/layout/keyguard_screen_password_landscape.xml
@@ -24,7 +24,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:rowCount="10"
+ android:rowCount="11"
android:id="@+id/root"
android:clipChildren="false">
@@ -33,7 +33,8 @@
<com.android.internal.widget.DigitalClock android:id="@+id/time"
android:layout_marginTop="16dip"
- android:layout_marginBottom="8dip">
+ 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... -->
@@ -67,120 +68,129 @@
android:id="@+id/date"
android:layout_below="@id/time"
android:layout_marginTop="6dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
- android:layout_gravity="left"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/alarm_status"
+ android:singleLine="true"
+ android:ellipsize="marquee"
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"
+ android:layout_gravity="right"
/>
<TextView
android:id="@+id/status1"
android:layout_marginTop="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
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:singleLine="true"
+ android:ellipsize="marquee"
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:singleLine="true"
+ android:ellipsize="marquee"
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="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:layout_width="match_parent"
+ <TextView
+ android:id="@+id/carrier"
android:singleLine="true"
- android:textStyle="normal"
- android:inputType="textPassword"
- android:gravity="center"
- android:textSize="24sp"
+ android:ellipsize="marquee"
+ android:layout_gravity="right"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:background="@drawable/lockscreen_password_field_dark"
- android:textColor="?android:attr/textColorPrimary"
- />
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:textColor="?android:attr/textColorSecondary"
+ />
- <!-- 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"
+ <!-- "emergency calls only" shown when sim is missing or PUKd -->
+ <TextView
+ android:id="@+id/emergencyCallText"
+ android:layout_marginTop="20dip"
+ android:layout_gravity="right"
+ 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_gravity="right"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
+ android:text="@string/lockscreen_emergency_call"
+ style="?android:attr/buttonBarButtonStyle"
+ android:drawablePadding="8dip"
android:visibility="visible"
/>
+ <!-- Column 1 -->
+ <Space android:layout_columnWeight="1" android:layout_rowSpan="11" />
+
+ <!-- Column 2 - password entry field and PIN keyboard -->
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:layout_gravity="center|fill"
+ android:layout_rowSpan="11">
+
+ <EditText android:id="@+id/passwordEntry"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:singleLine="true"
+ android:textStyle="normal"
+ android:inputType="textPassword"
+ android:layout_gravity="center"
+ android:textSize="24sp"
+ android:minEms="8"
+ 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="250dip"
+ android:layout_height="100dip"
+ android:layout_gravity="center"
+ android:background="#40000000"
+ android:layout_marginTop="5dip"
+ android:keyBackground="@drawable/btn_keyboard_key_fulltrans"
+ android:visibility="visible"
+ />
+
+ </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 30747bc..6953ac8 100644
--- a/core/res/res/layout/keyguard_screen_password_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_password_portrait.xml
@@ -41,10 +41,10 @@
android:layout_marginTop="4dip"
android:layout_marginRight="16dip"
android:layout_gravity="right"
- android:drawableLeft="@drawable/ic_emergency"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
android:drawablePadding="8dip"
android:text="@string/lockscreen_emergency_call"
- style="@style/Widget.Button.Transparent"
+ style="?android:attr/buttonBarButtonStyle"
/>
<!-- bottom: password -->
diff --git a/core/res/res/layout/keyguard_screen_status_land.xml b/core/res/res/layout/keyguard_screen_status_land.xml
index 60dd9ff..021faa3 100644
--- a/core/res/res/layout/keyguard_screen_status_land.xml
+++ b/core/res/res/layout/keyguard_screen_status_land.xml
@@ -73,6 +73,8 @@
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
@@ -81,6 +83,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="30dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
@@ -92,6 +96,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_marginTop="10dip"
@@ -106,6 +112,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:singleLine="true"
+ android:ellipsize="marquee"
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 e72df5b..f84e61c2 100644
--- a/core/res/res/layout/keyguard_screen_status_port.xml
+++ b/core/res/res/layout/keyguard_screen_status_port.xml
@@ -70,6 +70,8 @@
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"/>
@@ -78,6 +80,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"/>
@@ -90,6 +94,8 @@
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_marginTop="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:textAppearance="?android:attr/textAppearanceMedium"
/>
@@ -101,6 +107,8 @@
android:layout_height="wrap_content"
android:layout_gravity="right"
android:layout_alignParentTop="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_marginTop="4dip"
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
index 020bb27..1e2abf9 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -80,6 +80,8 @@
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
/>
@@ -88,6 +90,8 @@
android:id="@+id/alarm_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -105,6 +109,8 @@
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:layout_alignParentRight="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -119,6 +125,8 @@
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:layout_alignParentRight="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -133,6 +141,8 @@
android:layout_marginLeft="16dip"
android:layout_marginRight="16dip"
android:layout_alignParentRight="true"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_marginTop="12dip"
@@ -147,14 +157,13 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dip"
android:layout_marginRight="16dip"
- android:drawableLeft="@drawable/ic_emergency"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
android:layout_alignParentRight="true"
android:layout_below="@id/screenLocked"
- style="@style/Widget.Button.Transparent"
+ style="?android:attr/buttonBarButtonStyle"
android:drawablePadding="4dip"
android:text="@string/lockscreen_emergency_call"
android:visibility="visible"
- android:background="@null"
/>
<RelativeLayout
@@ -207,6 +216,8 @@
android:layout_marginTop="0dip"
android:layout_marginRight="8dip"
android:text="@string/emergency_calls_only"
+ 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"
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 930ac33..5588adc 100644
--- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -68,6 +68,8 @@
android:id="@+id/date"
android:layout_below="@id/time"
android:layout_marginTop="6dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
@@ -75,6 +77,8 @@
<TextView
android:id="@+id/alarm_status"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -85,6 +89,8 @@
<TextView
android:id="@+id/status1"
android:layout_marginTop="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -94,6 +100,8 @@
<TextView
android:id="@+id/status2"
android:layout_marginTop="4dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -102,6 +110,8 @@
<TextView
android:id="@+id/screenLocked"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:gravity="center"
@@ -113,7 +123,8 @@
<Space android:height="20dip"/>
<LinearLayout android:orientation="vertical"
- android:gravity="fill">
+ android:layout_gravity="right"
+ android:gravity="fill_horizontal">
<TextView
android:id="@+id/carrier"
@@ -148,11 +159,10 @@
android:id="@+id/emergencyCallButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:drawableLeft="@drawable/ic_emergency"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
android:text="@string/lockscreen_emergency_call"
- style="@style/Widget.Button.Transparent"
+ style="?android:attr/buttonBarButtonStyle"
android:drawablePadding="8dip"
- android:layout_marginRight="80dip"
android:visibility="visible"
android:layout_gravity="right"
/>
diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml
index 8319b8a..d0538dd 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="8">
+ android:rowCount="7">
<!-- Column 0: Time, date and status -->
<com.android.internal.widget.DigitalClock android:id="@+id/time"
@@ -68,6 +68,8 @@
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
@@ -77,6 +79,8 @@
android:id="@+id/alarm_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
@@ -87,6 +91,8 @@
android:id="@+id/status1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:layout_gravity="right"
@@ -100,31 +106,43 @@
<TextView android:id="@+id/carrier"
android:layout_gravity="right"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:singleLine="true"
android:ellipsize="marquee"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
/>
- <Button android:id="@+id/emergencyCallButton"
- android:layout_gravity="right"
- style="@style/Widget.Button.Transparent"
- 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"
- />
+ <LinearLayout
+ style="?android:attr/buttonBarStyle"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="right">
- <Button android:id="@+id/forgotPatternButton"
- android:layout_gravity="right"
- 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"
- />
+ <Button android:id="@+id/emergencyCallButton"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
+ android:text="@string/lockscreen_emergency_call"
+ android:drawableLeft="@drawable/lockscreen_emergency_button"
+ android:drawablePadding="0dip"
+ />
+
+ <Button android:id="@+id/forgotPatternButton"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_gravity="right"
+ android:layout_width="wrap_content"
+ android:layout_height="0dip"
+ android:layout_weight="1"
+ 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"
+ />
+ </LinearLayout>
<!-- Column 1: lock pattern -->
<com.android.internal.widget.LockPatternView android:id="@+id/lockPattern"
@@ -134,6 +152,6 @@
android:layout_marginRight="8dip"
android:layout_marginBottom="8dip"
android:layout_marginLeft="8dip"
- android:layout_rowSpan="8"/>
+ android:layout_rowSpan="7"/>
</GridLayout>
diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
index bbe2006..774f830 100644
--- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml
+++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml
@@ -68,6 +68,8 @@
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
/>
@@ -77,6 +79,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dip"
+ android:singleLine="true"
+ android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textSize="@dimen/keyguard_lockscreen_status_line_font_size"
android:drawablePadding="4dip"
@@ -89,22 +93,22 @@
android:id="@+id/status1"
android:layout_gravity="right"
android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
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_gravity="right"
android:layout_marginRight="@dimen/keyguard_lockscreen_status_line_font_right_margin"
+ android:singleLine="true"
+ android:ellipsize="marquee"
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"
/>
@@ -136,30 +140,33 @@
<!-- Footer: an emergency call button and an initially hidden "Forgot pattern" button -->
<LinearLayout
android:orientation="horizontal"
- android:layout_gravity="fill_horizontal">
+ android:layout_width="match_parent"
+ style="?android:attr/buttonBarStyle"
+ android:gravity="center"
+ android:weightSum="2">
<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:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
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"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
- style="@style/Widget.Button.Transparent"
+ android:layout_width="0dip"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="?android:attr/buttonBarButtonStyle"
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 4dd9334..ec2313c 100644
--- a/core/res/res/values-land/dimens.xml
+++ b/core/res/res/values-land/dimens.xml
@@ -33,8 +33,12 @@
<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 on Unsecure unlock screen. -->
- <dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
+ <dimen name="keyguard_lockscreen_clock_font_size">70sp</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/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/attrs.xml b/core/res/res/values/attrs.xml
index 58d25d6..2f714f6 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2125,6 +2125,12 @@
<!-- Specifies extra space on the bottom side of this view.
This space is outside this view's bounds. -->
<attr name="layout_marginBottom" format="dimension" />
+ <!-- Specifies extra space on the start side of this view.
+ This space is outside this view's bounds. -->
+ <attr name="layout_marginStart" format="dimension" />
+ <!-- Specifies extra space on the end side of this view.
+ This space is outside this view's bounds. -->
+ <attr name="layout_marginEnd" format="dimension" />
</declare-styleable>
<!-- Use <code>input-method</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index a8b7b75..0ad3184 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -115,6 +115,14 @@
<item>"mobile_cbs,12,0,2,60000,true"</item>
</string-array>
+ <!-- Array of ConnectivityManager.TYPE_xxxx constants for networks that may only
+ be controlled by systemOrSignature apps. -->
+ <integer-array translatable="false" name="config_protectedNetworks">
+ <item>10</item>
+ <item>11</item>
+ <item>12</item>
+ </integer-array>
+
<!-- This string array should be overridden by the device to present a list of radio
attributes. This is used by the connectivity manager to decide which networks can coexist
based on the hardware -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 9933b6f..a5e5f70 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -119,6 +119,10 @@
<dimen name="action_bar_default_height">48dip</dimen>
<!-- Vertical padding around action bar icons. -->
<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 on Unsecure unlock screen. -->
<dimen name="keyguard_lockscreen_clock_font_size">80sp</dimen>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 580c204..ec191dd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1778,4 +1778,7 @@
<public type="attr" name="paddingStart"/>
<public type="attr" name="paddingEnd"/>
+ <public type="attr" name="layout_marginStart"/>
+ <public type="attr" name="layout_marginEnd"/>
+
</resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 67be63e..5244b74 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -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">
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/net/http/DefaultHttpClientTest.java b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
index da77298..cf9e6e6 100644
--- a/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
+++ b/core/tests/coretests/src/android/net/http/DefaultHttpClientTest.java
@@ -28,9 +28,17 @@
import java.io.StringWriter;
import junit.framework.TestCase;
import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthenticationException;
+import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicHeader;
+/**
+ * Tests for various regressions and problems with DefaultHttpClient. This is
+ * not a comprehensive test!
+ */
public final class DefaultHttpClientTest extends TestCase {
private MockWebServer server = new MockWebServer();
@@ -87,4 +95,40 @@
reader.close();
return writer.toString();
}
+
+ // http://code.google.com/p/android/issues/detail?id=16051
+ public void testDigestSchemeAlgorithms() throws Exception {
+ authenticateDigestAlgorithm("MD5");
+ authenticateDigestAlgorithm("MD5-sess");
+ authenticateDigestAlgorithm("md5");
+ authenticateDigestAlgorithm("md5-sess");
+ authenticateDigestAlgorithm("md5-SESS");
+ authenticateDigestAlgorithm("MD5-SESS");
+ try {
+ authenticateDigestAlgorithm("MD5-");
+ } catch (AuthenticationException expected) {
+ }
+ try {
+ authenticateDigestAlgorithm("MD6");
+ } catch (AuthenticationException expected) {
+ }
+ try {
+ authenticateDigestAlgorithm("MD");
+ } catch (AuthenticationException expected) {
+ }
+ try {
+ authenticateDigestAlgorithm("");
+ } catch (AuthenticationException expected) {
+ }
+ }
+
+ private void authenticateDigestAlgorithm(String algorithm) throws Exception {
+ String challenge = "Digest realm=\"protected area\", "
+ + "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+ + "algorithm=" + algorithm;
+ DigestScheme digestScheme = new DigestScheme();
+ digestScheme.processChallenge(new BasicHeader("WWW-Authenticate", challenge));
+ HttpGet get = new HttpGet();
+ digestScheme.authenticate(new UsernamePasswordCredentials("username", "password"), get);
+ }
}
diff --git a/core/tests/notificationtests/Android.mk b/core/tests/notificationtests/Android.mk
new file mode 100644
index 0000000..be2e6bf
--- /dev/null
+++ b/core/tests/notificationtests/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_PACKAGE_NAME := NotificationStressTests
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/core/tests/notificationtests/AndroidManifest.xml b/core/tests/notificationtests/AndroidManifest.xml
new file mode 100644
index 0000000..51e530a
--- /dev/null
+++ b/core/tests/notificationtests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.notification.tests" >
+
+ <application >
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="com.android.notification.tests"
+ android:label="Notification Stress Tests" />
+
+</manifest>
diff --git a/core/tests/notificationtests/src/android/app/NotificationStressTest.java b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
new file mode 100644
index 0000000..52ea1c4
--- /dev/null
+++ b/core/tests/notificationtests/src/android/app/NotificationStressTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.app;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.test.InstrumentationTestCase;
+import android.test.RepetitiveTest;
+import android.test.TimedTest;
+
+import java.util.Random;
+
+/**
+ * Test which spams notification manager with a large number of notifications, for both stress and
+ * performance testing.
+ */
+public class NotificationStressTest extends InstrumentationTestCase {
+
+ private static final int NUM_ITERATIONS = 200;
+ private static final int[] ICONS = new int[] {
+ android.R.drawable.stat_notify_call_mute,
+ android.R.drawable.stat_notify_chat,
+ android.R.drawable.stat_notify_error,
+ android.R.drawable.stat_notify_missed_call,
+ android.R.drawable.stat_notify_more,
+ android.R.drawable.stat_notify_sdcard,
+ android.R.drawable.stat_notify_sdcard_prepare,
+ android.R.drawable.stat_notify_sdcard_usb,
+ android.R.drawable.stat_notify_sync,
+ android.R.drawable.stat_notify_sync_noanim,
+ android.R.drawable.stat_notify_voicemail,
+ };
+
+ private final Random mRandom = new Random();
+ private Context mContext;
+ private NotificationManager mNotificationManager;
+ private int notifyId = 0;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mContext = getInstrumentation().getContext();
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ mNotificationManager.cancelAll();
+ }
+
+ @RepetitiveTest(numIterations=NUM_ITERATIONS)
+ public void testNotificationStress() {
+ // Cancel one of every five notifications to vary load on notification manager
+ if (notifyId % 5 == 4) {
+ mNotificationManager.cancel(notifyId - 4);
+ }
+ sendNotification(notifyId++, "testNotificationStressNotify");
+ }
+
+ private void sendNotification(int id, CharSequence text) {
+ // Create "typical" notification with random icon
+ Notification notification = new Notification(ICONS[mRandom.nextInt(ICONS.length)], text,
+ System.currentTimeMillis());
+ // Fill in arbitrary content
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ CharSequence title = text + " " + id;
+ CharSequence subtitle = String.valueOf(System.currentTimeMillis());
+ notification.setLatestEventInfo(mContext, title, subtitle, pendingIntent);
+ mNotificationManager.notify(id, notification);
+ SystemClock.sleep(10);
+ }
+}
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/developing/device.jd b/docs/html/guide/developing/device.jd
index 3127e10..cb5a0b6 100644
--- a/docs/html/guide/developing/device.jd
+++ b/docs/html/guide/developing/device.jd
@@ -145,6 +145,9 @@
<td>Kyocera</td>
<td><code>0482</code></td></tr>
<tr>
+ <td>Lenevo</td>
+ <td><code>17EF</code></td></tr>
+ <tr>
<td>LG</td>
<td><code>1004</code></td></tr>
<tr>
diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs
index 75b8ead..abea85b 100644
--- a/docs/html/guide/guide_toc.cs
+++ b/docs/html/guide/guide_toc.cs
@@ -271,7 +271,7 @@
<li><a href="<?cs var:toroot ?>guide/topics/media/index.html">
<span class="en">Media</span>
- </a><span class="new">updated!</span></li>
+ </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/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 d3ef70a..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
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/wireless/bluetooth.jd b/docs/html/guide/topics/wireless/bluetooth.jd
index a6c46d2..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>
@@ -69,14 +73,14 @@
<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/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd
index 27e742a..3c2ba8b 100644
--- a/docs/html/sdk/oem-usb.jd
+++ b/docs/html/sdk/oem-usb.jd
@@ -80,6 +80,12 @@
<td><a href="http://www.kyocera-wireless.com/support/phone_drivers.htm">http://www.kyocera-wireless.com/support/phone_drivers.htm</a>
</td>
</tr>
+ <tr>
+ <td>Lenevo</td>
+ <td><a href="http://developer.lenovomm.com/developer/download.jsp"
+ >http://developer.lenovomm.com/developer/download.jsp</a>
+ </td>
+ </tr>
<tr><td>LGE</td> <td><a
href="http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp">http://www.lg.com/us/mobile-phones/mobile-support/mobile-lg-mobile-phone-support.jsp</a></td>
</tr><tr><td>Motorola</td> <td><a
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/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index a9414e8..b0f7fd3 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -167,6 +167,7 @@
}
if (mCurrDrawable != null) {
mCurrDrawable.jumpToCurrentState();
+ mCurrDrawable.setAlpha(mAlpha);
}
if (mExitAnimationEnd != 0) {
mExitAnimationEnd = 0;
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/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/camera/Camera.h b/include/camera/Camera.h
index 7106bfa..f701280 100644
--- a/include/camera/Camera.h
+++ b/include/camera/Camera.h
@@ -18,9 +18,11 @@
#define ANDROID_HARDWARE_CAMERA_H
#include <utils/Timers.h>
-#include <camera/ICameraClient.h>
#include <gui/ISurfaceTexture.h>
#include <system/camera.h>
+#include <camera/ICameraClient.h>
+#include <camera/ICameraRecordingProxy.h>
+#include <camera/ICameraRecordingProxyListener.h>
namespace android {
@@ -70,7 +72,7 @@
static status_t getCameraInfo(int cameraId,
struct CameraInfo* cameraInfo);
static sp<Camera> connect(int cameraId);
- ~Camera();
+ virtual ~Camera();
void init();
status_t reconnect();
@@ -129,8 +131,11 @@
status_t storeMetaDataInBuffers(bool enabled);
void setListener(const sp<CameraListener>& listener);
+ void setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener);
void setPreviewCallbackFlags(int preview_callback_flag);
+ sp<ICameraRecordingProxy> getRecordingProxy();
+
// ICameraClient interface
virtual void notifyCallback(int32_t msgType, int32_t ext, int32_t ext2);
virtual void dataCallback(int32_t msgType, const sp<IMemory>& dataPtr);
@@ -138,6 +143,20 @@
sp<ICamera> remote();
+ class RecordingProxy : public BnCameraRecordingProxy
+ {
+ public:
+ RecordingProxy(const sp<Camera>& camera);
+
+ // ICameraRecordingProxy interface
+ virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener);
+ virtual void stopRecording();
+ virtual void releaseRecordingFrame(const sp<IMemory>& mem);
+
+ private:
+ sp<Camera> mCamera;
+ };
+
private:
Camera();
Camera(const Camera&);
@@ -162,12 +181,12 @@
status_t mStatus;
sp<CameraListener> mListener;
+ sp<ICameraRecordingProxyListener> mRecordingProxyListener;
friend class DeathNotifier;
static Mutex mLock;
static sp<ICameraService> mCameraService;
-
};
}; // namespace android
diff --git a/include/camera/ICameraRecordingProxy.h b/include/camera/ICameraRecordingProxy.h
new file mode 100644
index 0000000..2aac284
--- /dev/null
+++ b/include/camera/ICameraRecordingProxy.h
@@ -0,0 +1,101 @@
+/*
+ * 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 ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H
+#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_H
+
+#include <binder/IInterface.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+class ICameraRecordingProxyListener;
+class IMemory;
+class Parcel;
+
+/*
+ * The purpose of ICameraRecordingProxy and ICameraRecordingProxyListener is to
+ * allow applications using the camera during recording.
+ *
+ * Camera service allows only one client at a time. Since camcorder application
+ * needs to own the camera to do things like zoom, the media recorder cannot
+ * access the camera directly during recording. So ICameraRecordingProxy is a
+ * proxy of ICamera, which allows the media recorder to start/stop the recording
+ * and release recording frames. ICameraRecordingProxyListener is an interface
+ * that allows the recorder to receive video frames during recording.
+ *
+ * ICameraRecordingProxy
+ * startRecording()
+ * stopRecording()
+ * releaseRecordingFrame()
+ *
+ * ICameraRecordingProxyListener
+ * dataCallbackTimestamp()
+
+ * The camcorder app opens the camera and starts the preview. The app passes
+ * ICamera and ICameraRecordingProxy to the media recorder by
+ * MediaRecorder::setCamera(). The recorder uses ICamera to setup the camera in
+ * MediaRecorder::start(). After setup, the recorder disconnects from camera
+ * service. The recorder calls ICameraRecordingProxy::startRecording() and
+ * passes a ICameraRecordingProxyListener to the app. The app connects back to
+ * camera service and starts the recording. The app owns the camera and can do
+ * things like zoom. The media recorder receives the video frames from the
+ * listener and releases them by ICameraRecordingProxy::releaseRecordingFrame.
+ * The recorder calls ICameraRecordingProxy::stopRecording() to stop the
+ * recording.
+ *
+ * The call sequences are as follows:
+ * 1. The app: Camera.unlock().
+ * 2. The app: MediaRecorder.setCamera().
+ * 3. Start recording
+ * (1) The app: MediaRecorder.start().
+ * (2) The recorder: ICamera.unlock() and ICamera.disconnect().
+ * (3) The recorder: ICameraRecordingProxy.startRecording().
+ * (4) The app: ICamera.reconnect().
+ * (5) The app: ICamera.startRecording().
+ * 4. During recording
+ * (1) The recorder: receive frames from ICameraRecordingProxyListener.dataCallbackTimestamp()
+ * (2) The recorder: release frames by ICameraRecordingProxy.releaseRecordingFrame().
+ * 5. Stop recording
+ * (1) The app: MediaRecorder.stop()
+ * (2) The recorder: ICameraRecordingProxy.stopRecording().
+ * (3) The app: ICamera.stopRecording().
+ */
+
+class ICameraRecordingProxy: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(CameraRecordingProxy);
+
+ virtual status_t startRecording(const sp<ICameraRecordingProxyListener>& listener) = 0;
+ virtual void stopRecording() = 0;
+ virtual void releaseRecordingFrame(const sp<IMemory>& mem) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnCameraRecordingProxy: public BnInterface<ICameraRecordingProxy>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif
diff --git a/include/camera/ICameraRecordingProxyListener.h b/include/camera/ICameraRecordingProxyListener.h
new file mode 100644
index 0000000..b6c0624
--- /dev/null
+++ b/include/camera/ICameraRecordingProxyListener.h
@@ -0,0 +1,52 @@
+/*
+ * 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 ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
+#define ANDROID_HARDWARE_ICAMERA_RECORDING_PROXY_LISTENER_H
+
+#include <binder/IInterface.h>
+#include <stdint.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class Parcel;
+class IMemory;
+
+class ICameraRecordingProxyListener: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(CameraRecordingProxyListener);
+
+ virtual void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType,
+ const sp<IMemory>& data) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnCameraRecordingProxyListener: public BnInterface<ICameraRecordingProxyListener>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif
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/IMediaRecorder.h b/include/media/IMediaRecorder.h
index 28be7c1..a73267d 100644
--- a/include/media/IMediaRecorder.h
+++ b/include/media/IMediaRecorder.h
@@ -24,6 +24,7 @@
class Surface;
class ICamera;
+class ICameraRecordingProxy;
class IMediaRecorderClient;
class IMediaRecorder: public IInterface
@@ -31,28 +32,29 @@
public:
DECLARE_META_INTERFACE(MediaRecorder);
- virtual status_t setCamera(const sp<ICamera>& camera) = 0;
- virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
- virtual status_t setVideoSource(int vs) = 0;
- virtual status_t setAudioSource(int as) = 0;
- virtual status_t setOutputFormat(int of) = 0;
- virtual status_t setVideoEncoder(int ve) = 0;
- virtual status_t setAudioEncoder(int ae) = 0;
- virtual status_t setOutputFile(const char* path) = 0;
- virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
- virtual status_t setOutputFileAuxiliary(int fd) = 0;
- virtual status_t setVideoSize(int width, int height) = 0;
- virtual status_t setVideoFrameRate(int frames_per_second) = 0;
- virtual status_t setParameters(const String8& params) = 0;
- virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0;
- virtual status_t prepare() = 0;
- virtual status_t getMaxAmplitude(int* max) = 0;
- virtual status_t start() = 0;
- virtual status_t stop() = 0;
- virtual status_t reset() = 0;
- virtual status_t init() = 0;
- virtual status_t close() = 0;
- virtual status_t release() = 0;
+ virtual status_t setCamera(const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy) = 0;
+ virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
+ virtual status_t setVideoSource(int vs) = 0;
+ virtual status_t setAudioSource(int as) = 0;
+ virtual status_t setOutputFormat(int of) = 0;
+ virtual status_t setVideoEncoder(int ve) = 0;
+ virtual status_t setAudioEncoder(int ae) = 0;
+ virtual status_t setOutputFile(const char* path) = 0;
+ virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
+ virtual status_t setOutputFileAuxiliary(int fd) = 0;
+ virtual status_t setVideoSize(int width, int height) = 0;
+ virtual status_t setVideoFrameRate(int frames_per_second) = 0;
+ virtual status_t setParameters(const String8& params) = 0;
+ virtual status_t setListener(const sp<IMediaRecorderClient>& listener) = 0;
+ virtual status_t prepare() = 0;
+ virtual status_t getMaxAmplitude(int* max) = 0;
+ virtual status_t start() = 0;
+ virtual status_t stop() = 0;
+ virtual status_t reset() = 0;
+ virtual status_t init() = 0;
+ virtual status_t close() = 0;
+ virtual status_t release() = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/media/MediaRecorderBase.h b/include/media/MediaRecorderBase.h
index 7e22a24..1c08969 100644
--- a/include/media/MediaRecorderBase.h
+++ b/include/media/MediaRecorderBase.h
@@ -24,6 +24,7 @@
namespace android {
+class ICameraRecordingProxy;
class Surface;
struct MediaRecorderBase {
@@ -38,7 +39,8 @@
virtual status_t setVideoEncoder(video_encoder ve) = 0;
virtual status_t setVideoSize(int width, int height) = 0;
virtual status_t setVideoFrameRate(int frames_per_second) = 0;
- virtual status_t setCamera(const sp<ICamera>& camera) = 0;
+ virtual status_t setCamera(const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy) = 0;
virtual status_t setPreviewSurface(const sp<Surface>& surface) = 0;
virtual status_t setOutputFile(const char *path) = 0;
virtual status_t setOutputFile(int fd, int64_t offset, int64_t length) = 0;
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/mediarecorder.h b/include/media/mediarecorder.h
index 36bf34e..af12d3c 100644
--- a/include/media/mediarecorder.h
+++ b/include/media/mediarecorder.h
@@ -30,6 +30,7 @@
class Surface;
class IMediaRecorder;
class ICamera;
+class ICameraRecordingProxy;
typedef void (*media_completion_f)(status_t status, void *cookie);
@@ -202,7 +203,7 @@
void died();
status_t initCheck();
- status_t setCamera(const sp<ICamera>& camera);
+ status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy);
status_t setPreviewSurface(const sp<Surface>& surface);
status_t setVideoSource(int vs);
status_t setAudioSource(int as);
diff --git a/include/media/stagefright/CameraSource.h b/include/media/stagefright/CameraSource.h
index bb25bae3..80b7c1c 100644
--- a/include/media/stagefright/CameraSource.h
+++ b/include/media/stagefright/CameraSource.h
@@ -21,6 +21,7 @@
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaSource.h>
#include <camera/ICamera.h>
+#include <camera/ICameraRecordingProxyListener.h>
#include <camera/CameraParameters.h>
#include <utils/List.h>
#include <utils/RefBase.h>
@@ -68,6 +69,7 @@
* @return NULL on error.
*/
static CameraSource *CreateFromCamera(const sp<ICamera> &camera,
+ const sp<ICameraRecordingProxy> &proxy,
int32_t cameraId,
Size videoSize,
int32_t frameRate,
@@ -111,6 +113,23 @@
virtual void signalBufferReturned(MediaBuffer* buffer);
protected:
+ class ProxyListener: public BnCameraRecordingProxyListener {
+ public:
+ ProxyListener(const sp<CameraSource>& source);
+ virtual void dataCallbackTimestamp(int64_t timestampUs, int32_t msgType,
+ const sp<IMemory> &data);
+
+ private:
+ sp<CameraSource> mSource;
+ };
+
+ // isBinderAlive needs linkToDeath to work.
+ class DeathNotifier: public IBinder::DeathRecipient {
+ public:
+ DeathNotifier() {}
+ virtual void binderDied(const wp<IBinder>& who);
+ };
+
enum CameraFlags {
FLAGS_SET_CAMERA = 1L << 0,
FLAGS_HOT_CAMERA = 1L << 1,
@@ -123,6 +142,8 @@
status_t mInitCheck;
sp<Camera> mCamera;
+ sp<ICameraRecordingProxy> mCameraRecordingProxy;
+ sp<DeathNotifier> mDeathNotifier;
sp<Surface> mSurface;
sp<MetaData> mMeta;
@@ -132,7 +153,8 @@
bool mStarted;
int32_t mNumFramesEncoded;
- CameraSource(const sp<ICamera>& camera, int32_t cameraId,
+ CameraSource(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+ int32_t cameraId,
Size videoSize, int32_t frameRate,
const sp<Surface>& surface,
bool storeMetaDataInVideoBuffers);
@@ -172,10 +194,12 @@
void releaseOneRecordingFrame(const sp<IMemory>& frame);
- status_t init(const sp<ICamera>& camera, int32_t cameraId,
- Size videoSize, int32_t frameRate,
- bool storeMetaDataInVideoBuffers);
- status_t isCameraAvailable(const sp<ICamera>& camera, int32_t cameraId);
+ status_t init(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+ int32_t cameraId, Size videoSize, int32_t frameRate,
+ bool storeMetaDataInVideoBuffers);
+ status_t isCameraAvailable(const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy,
+ int32_t cameraId);
status_t isCameraColorFormatSupported(const CameraParameters& params);
status_t configureCamera(CameraParameters* params,
int32_t width, int32_t height,
diff --git a/include/media/stagefright/CameraSourceTimeLapse.h b/include/media/stagefright/CameraSourceTimeLapse.h
index 0e5d534..f07ebba 100644
--- a/include/media/stagefright/CameraSourceTimeLapse.h
+++ b/include/media/stagefright/CameraSourceTimeLapse.h
@@ -33,6 +33,7 @@
public:
static CameraSourceTimeLapse *CreateFromCamera(
const sp<ICamera> &camera,
+ const sp<ICameraRecordingProxy> &proxy,
int32_t cameraId,
Size videoSize,
int32_t videoFrameRate,
@@ -132,6 +133,7 @@
CameraSourceTimeLapse(
const sp<ICamera> &camera,
+ const sp<ICameraRecordingProxy> &proxy,
int32_t cameraId,
Size videoSize,
int32_t videoFrameRate,
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/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/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index fd6c22c..ab4b9e0 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -71,6 +71,13 @@
/** Data type for PKCS12. */
public static final String PKCS12 = "PKCS12";
+ // historically used by Android
+ public static final String EXTENSION_CRT = ".crt";
+ public static final String EXTENSION_P12 = ".p12";
+ // commonly used on Windows
+ public static final String EXTENSION_CER = ".cer";
+ public static final String EXTENSION_PFX = ".pfx";
+
/**
* Convert objects to a PEM format, which is used for
* CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
@@ -130,6 +137,15 @@
return intent;
}
+ public void install(Context context) {
+ try {
+ Intent intent = createInstallIntent();
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.w(LOGTAG, e.toString());
+ }
+ }
+
public void install(Context context, KeyPair pair) {
try {
Intent intent = createInstallIntent();
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index 4f1596d..18011e6 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -22,6 +22,7 @@
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -93,6 +94,26 @@
public static final String EXTRA_RESPONSE = "response";
/**
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_HOST = "host";
+
+ /**
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_PORT = "port";
+
+ /**
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_ALIAS = "alias";
+
+ /**
+ * @hide Also used by KeyChainActivity implementation
+ */
+ public static final String EXTRA_SENDER = "sender";
+
+ /**
* Launches an {@code Activity} for the user to select the alias
* for a private key and certificate pair for authentication. The
* selected alias or null will be returned via the
@@ -106,6 +127,9 @@
* <p>{@code host} and {@code port} may be used to give the user
* more context about the server requesting the credentials.
*
+ * <p>{@code alias} allows the chooser to preselect an existing
+ * alias which will still be subject to user confirmation.
+ *
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#USE_CREDENTIALS}.
*
@@ -123,14 +147,17 @@
* certificate, or null if unavailable.
* @param port The port number of the server requesting the
* certificate, or -1 if unavailable.
+ * @param alias The alias to preselect if available, or null if
+ * unavailable.
*/
public static void choosePrivateKeyAlias(Activity activity, KeyChainAliasCallback response,
String[] keyTypes, Principal[] issuers,
- String host, int port) {
+ String host, int port,
+ String alias) {
/*
- * TODO currently keyTypes, issuers, host, and port are
- * unused. They are meant to follow the semantics and purpose
- * of X509KeyManager method arguments.
+ * TODO currently keyTypes, issuers are unused. They are meant
+ * to follow the semantics and purpose of X509KeyManager
+ * method arguments.
*
* keyTypes would allow the list to be filtered and typically
* will be set correctly by the server. In practice today,
@@ -142,11 +169,6 @@
* server. Others will send none. If this is used, if there
* are no matches after applying the constraint, it should be
* ignored.
- *
- * host and port may be shown to the user if available, but it
- * should be clear that they are not validated values, perhaps
- * shown along with requesting application identity to clarify
- * the source of the request.
*/
if (activity == null) {
throw new NullPointerException("activity == null");
@@ -156,6 +178,11 @@
}
Intent intent = new Intent("com.android.keychain.CHOOSER");
intent.putExtra(EXTRA_RESPONSE, new AliasResponse(activity, response));
+ intent.putExtra(EXTRA_HOST, host);
+ intent.putExtra(EXTRA_PORT, port);
+ intent.putExtra(EXTRA_ALIAS, alias);
+ // the PendingIntent is used to get calling package name
+ intent.putExtra(EXTRA_SENDER, PendingIntent.getActivity(activity, 0, new Intent(), 0));
activity.startActivity(intent);
}
diff --git a/libs/camera/Android.mk b/libs/camera/Android.mk
index b17b3d2..dc00957 100644
--- a/libs/camera/Android.mk
+++ b/libs/camera/Android.mk
@@ -6,7 +6,9 @@
CameraParameters.cpp \
ICamera.cpp \
ICameraClient.cpp \
- ICameraService.cpp
+ ICameraService.cpp \
+ ICameraRecordingProxy.cpp \
+ ICameraRecordingProxyListener.cpp
LOCAL_SHARED_LIBRARIES := \
libcutils \
diff --git a/libs/camera/Camera.cpp b/libs/camera/Camera.cpp
index 5eb48da..3c00db5 100644
--- a/libs/camera/Camera.cpp
+++ b/libs/camera/Camera.cpp
@@ -19,11 +19,12 @@
#define LOG_TAG "Camera"
#include <utils/Log.h>
#include <utils/threads.h>
-
+#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/IMemory.h>
#include <camera/Camera.h>
+#include <camera/ICameraRecordingProxyListener.h>
#include <camera/ICameraService.h>
#include <surfaceflinger/Surface.h>
@@ -236,6 +237,10 @@
void Camera::stopRecording()
{
LOGV("stopRecording");
+ {
+ Mutex::Autolock _l(mLock);
+ mRecordingProxyListener.clear();
+ }
sp <ICamera> c = mCamera;
if (c == 0) return;
c->stopRecording();
@@ -327,6 +332,12 @@
mListener = listener;
}
+void Camera::setRecordingProxyListener(const sp<ICameraRecordingProxyListener>& listener)
+{
+ Mutex::Autolock _l(mLock);
+ mRecordingProxyListener = listener;
+}
+
void Camera::setPreviewCallbackFlags(int flag)
{
LOGV("setPreviewCallbackFlags");
@@ -364,6 +375,19 @@
// callback from camera service when timestamped frame is ready
void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
{
+ // If recording proxy listener is registered, forward the frame and return.
+ // The other listener (mListener) is ignored because the receiver needs to
+ // call releaseRecordingFrame.
+ sp<ICameraRecordingProxyListener> proxylistener;
+ {
+ Mutex::Autolock _l(mLock);
+ proxylistener = mRecordingProxyListener;
+ }
+ if (proxylistener != NULL) {
+ proxylistener->dataCallbackTimestamp(timestamp, msgType, dataPtr);
+ return;
+ }
+
sp<CameraListener> listener;
{
Mutex::Autolock _l(mLock);
@@ -389,4 +413,34 @@
LOGW("Camera server died!");
}
+sp<ICameraRecordingProxy> Camera::getRecordingProxy() {
+ LOGV("getProxy");
+ return new RecordingProxy(this);
+}
+
+status_t Camera::RecordingProxy::startRecording(const sp<ICameraRecordingProxyListener>& listener)
+{
+ LOGV("RecordingProxy::startRecording");
+ mCamera->setRecordingProxyListener(listener);
+ mCamera->reconnect();
+ return mCamera->startRecording();
+}
+
+void Camera::RecordingProxy::stopRecording()
+{
+ LOGV("RecordingProxy::stopRecording");
+ mCamera->stopRecording();
+}
+
+void Camera::RecordingProxy::releaseRecordingFrame(const sp<IMemory>& mem)
+{
+ LOGV("RecordingProxy::releaseRecordingFrame");
+ mCamera->releaseRecordingFrame(mem);
+}
+
+Camera::RecordingProxy::RecordingProxy(const sp<Camera>& camera)
+{
+ mCamera = camera;
+}
+
}; // namespace android
diff --git a/libs/camera/ICameraRecordingProxy.cpp b/libs/camera/ICameraRecordingProxy.cpp
new file mode 100644
index 0000000..64b6a5c
--- /dev/null
+++ b/libs/camera/ICameraRecordingProxy.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ICameraRecordingProxy"
+#include <camera/ICameraRecordingProxy.h>
+#include <camera/ICameraRecordingProxyListener.h>
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+#include <stdint.h>
+#include <utils/Log.h>
+
+namespace android {
+
+enum {
+ START_RECORDING = IBinder::FIRST_CALL_TRANSACTION,
+ STOP_RECORDING,
+ RELEASE_RECORDING_FRAME,
+};
+
+
+class BpCameraRecordingProxy: public BpInterface<ICameraRecordingProxy>
+{
+public:
+ BpCameraRecordingProxy(const sp<IBinder>& impl)
+ : BpInterface<ICameraRecordingProxy>(impl)
+ {
+ }
+
+ status_t startRecording(const sp<ICameraRecordingProxyListener>& listener)
+ {
+ LOGV("startRecording");
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+ data.writeStrongBinder(listener->asBinder());
+ remote()->transact(START_RECORDING, data, &reply);
+ return reply.readInt32();
+ }
+
+ void stopRecording()
+ {
+ LOGV("stopRecording");
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+ remote()->transact(STOP_RECORDING, data, &reply);
+ }
+
+ void releaseRecordingFrame(const sp<IMemory>& mem)
+ {
+ LOGV("releaseRecordingFrame");
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraRecordingProxy::getInterfaceDescriptor());
+ data.writeStrongBinder(mem->asBinder());
+ remote()->transact(RELEASE_RECORDING_FRAME, data, &reply);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CameraRecordingProxy, "android.hardware.ICameraRecordingProxy");
+
+// ----------------------------------------------------------------------
+
+status_t BnCameraRecordingProxy::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case START_RECORDING: {
+ LOGV("START_RECORDING");
+ CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+ sp<ICameraRecordingProxyListener> listener =
+ interface_cast<ICameraRecordingProxyListener>(data.readStrongBinder());
+ reply->writeInt32(startRecording(listener));
+ return NO_ERROR;
+ } break;
+ case STOP_RECORDING: {
+ LOGV("STOP_RECORDING");
+ CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+ stopRecording();
+ return NO_ERROR;
+ } break;
+ case RELEASE_RECORDING_FRAME: {
+ LOGV("RELEASE_RECORDING_FRAME");
+ CHECK_INTERFACE(ICameraRecordingProxy, data, reply);
+ sp<IMemory> mem = interface_cast<IMemory>(data.readStrongBinder());
+ releaseRecordingFrame(mem);
+ return NO_ERROR;
+ } break;
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
diff --git a/libs/camera/ICameraRecordingProxyListener.cpp b/libs/camera/ICameraRecordingProxyListener.cpp
new file mode 100644
index 0000000..f8cece5
--- /dev/null
+++ b/libs/camera/ICameraRecordingProxyListener.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ICameraRecordingProxyListener"
+#include <camera/ICameraRecordingProxyListener.h>
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+namespace android {
+
+enum {
+ DATA_CALLBACK_TIMESTAMP = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpCameraRecordingProxyListener: public BpInterface<ICameraRecordingProxyListener>
+{
+public:
+ BpCameraRecordingProxyListener(const sp<IBinder>& impl)
+ : BpInterface<ICameraRecordingProxyListener>(impl)
+ {
+ }
+
+ void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData)
+ {
+ LOGV("dataCallback");
+ Parcel data, reply;
+ data.writeInterfaceToken(ICameraRecordingProxyListener::getInterfaceDescriptor());
+ data.writeInt64(timestamp);
+ data.writeInt32(msgType);
+ data.writeStrongBinder(imageData->asBinder());
+ remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(CameraRecordingProxyListener, "android.hardware.ICameraRecordingProxyListener");
+
+// ----------------------------------------------------------------------
+
+status_t BnCameraRecordingProxyListener::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+ switch(code) {
+ case DATA_CALLBACK_TIMESTAMP: {
+ LOGV("DATA_CALLBACK_TIMESTAMP");
+ CHECK_INTERFACE(ICameraRecordingProxyListener, data, reply);
+ nsecs_t timestamp = data.readInt64();
+ int32_t msgType = data.readInt32();
+ sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
+ dataCallbackTimestamp(timestamp, msgType, imageData);
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
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/Caches.cpp b/libs/hwui/Caches.cpp
index b0d8cf9..563d7e4 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -57,7 +57,7 @@
LOGD("Enabling debug mode %d", mDebugLevel);
#if RENDER_LAYERS_AS_REGIONS
- LOGD("Layers will be composited as regions");
+ INIT_LOGD("Layers will be composited as regions");
#endif
}
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index dfb5d3b..e034a868 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -166,30 +166,6 @@
// Layers management
///////////////////////////////////////////////////////////////////////////////
-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 = !isOpaque;
- layer->empty = true;
- layer->fbo = 0;
- layer->colorFilter = NULL;
- layer->fbo = 0;
- layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
- layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
- layer->alpha = 255;
- layer->mode = SkXfermode::kSrcOver_Mode;
- layer->colorFilter = NULL;
- layer->region.clear();
-
- glActiveTexture(GL_TEXTURE0);
- glGenTextures(1, &layer->texture);
-
- return layer;
-}
-
Layer* LayerRenderer::createLayer(uint32_t width, uint32_t height, bool isOpaque) {
LAYER_RENDERER_LOGD("Creating new layer %dx%d", width, height);
@@ -269,6 +245,41 @@
return true;
}
+static void setTextureParameters(Layer* layer) {
+ glBindTexture(layer->renderTarget, layer->texture);
+
+ glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+}
+
+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 = !isOpaque;
+ layer->empty = true;
+ layer->fbo = 0;
+ layer->colorFilter = NULL;
+ layer->fbo = 0;
+ layer->layer.set(0.0f, 0.0f, 0.0f, 0.0f);
+ layer->texCoords.set(0.0f, 1.0f, 0.0f, 1.0f);
+ layer->alpha = 255;
+ layer->mode = SkXfermode::kSrcOver_Mode;
+ layer->colorFilter = NULL;
+ layer->region.clear();
+ layer->renderTarget = GL_NONE; // see ::updateTextureLayer()
+
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, &layer->texture);
+
+ return layer;
+}
+
void LayerRenderer::updateTextureLayer(Layer* layer, uint32_t width, uint32_t height,
bool isOpaque, GLenum renderTarget, float* transform) {
if (layer) {
@@ -279,15 +290,11 @@
layer->region.set(width, height);
layer->regionRect.set(0.0f, 0.0f, width, height);
layer->texTransform.load(transform);
- layer->renderTarget = renderTarget;
- glBindTexture(layer->renderTarget, layer->texture);
-
- glTexParameteri(layer->renderTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(layer->renderTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
- glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(layer->renderTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ if (renderTarget != layer->renderTarget) {
+ layer->renderTarget = renderTarget;
+ setTextureParameters(layer);
+ }
}
}
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/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/utils/BackupData.cpp b/libs/utils/BackupData.cpp
index f963058..87912639 100644
--- a/libs/utils/BackupData.cpp
+++ b/libs/utils/BackupData.cpp
@@ -285,7 +285,8 @@
break;
}
default:
- LOGD("Chunk header at %d has invalid type: 0x%08x", (int)m_pos, (int)m_header.type);
+ LOGD("Chunk header at %d has invalid type: 0x%08x",
+ (int)(m_pos - sizeof(m_header)), (int)m_header.type);
m_status = EINVAL;
}
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index dd45111..6f42596 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -16,17 +16,16 @@
package android.media;
-import android.media.CamcorderProfile;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Surface;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
+
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.lang.ref.WeakReference;
/**
@@ -112,7 +111,8 @@
/**
* Sets a Camera to use for recording. Use this function to switch
* quickly between preview and capture mode without a teardown of
- * the camera object. Must call before prepare().
+ * the camera object. {@link android.hardware.Camera#unlock()} should be
+ * called before this. Must call before prepare().
*
* @param c the Camera to use for recording
*/
@@ -718,6 +718,11 @@
* Begins capturing and encoding data to the file specified with
* setOutputFile(). Call this after prepare().
*
+ * <p>Since API level 13, if applications set a camera via
+ * {@link #setCamera(Camera)}, the apps can use the camera after this method
+ * call. The apps do not need to lock the camera again. The apps should not
+ * start another recording session during recording.
+ *
* @throws IllegalStateException if it is called before
* prepare().
*/
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/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 2f7d7ee..12391c8 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -158,7 +158,7 @@
}
sp<Camera> c = get_native_camera(env, camera, NULL);
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
- process_media_recorder_call(env, mr->setCamera(c->remote()),
+ process_media_recorder_call(env, mr->setCamera(c->remote(), c->getRecordingProxy()),
"java/lang/RuntimeException", "setCamera failed.");
}
diff --git a/media/libmedia/IMediaRecorder.cpp b/media/libmedia/IMediaRecorder.cpp
index 59cd1b7..a44ef5a 100644
--- a/media/libmedia/IMediaRecorder.cpp
+++ b/media/libmedia/IMediaRecorder.cpp
@@ -60,12 +60,13 @@
{
}
- status_t setCamera(const sp<ICamera>& camera)
+ status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy)
{
- LOGV("setCamera(%p)", camera.get());
+ LOGV("setCamera(%p,%p)", camera.get(), proxy.get());
Parcel data, reply;
data.writeInterfaceToken(IMediaRecorder::getInterfaceDescriptor());
data.writeStrongBinder(camera->asBinder());
+ data.writeStrongBinder(proxy->asBinder());
remote()->transact(SET_CAMERA, data, &reply);
return reply.readInt32();
}
@@ -434,7 +435,9 @@
LOGV("SET_CAMERA");
CHECK_INTERFACE(IMediaRecorder, data, reply);
sp<ICamera> camera = interface_cast<ICamera>(data.readStrongBinder());
- reply->writeInt32(setCamera(camera));
+ sp<ICameraRecordingProxy> proxy =
+ interface_cast<ICameraRecordingProxy>(data.readStrongBinder());
+ reply->writeInt32(setCamera(camera, proxy));
return NO_ERROR;
} break;
default:
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/libmedia/mediarecorder.cpp b/media/libmedia/mediarecorder.cpp
index 0100a17..9e4edd0 100644
--- a/media/libmedia/mediarecorder.cpp
+++ b/media/libmedia/mediarecorder.cpp
@@ -28,9 +28,9 @@
namespace android {
-status_t MediaRecorder::setCamera(const sp<ICamera>& camera)
+status_t MediaRecorder::setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy)
{
- LOGV("setCamera(%p)", camera.get());
+ LOGV("setCamera(%p,%p)", camera.get(), proxy.get());
if(mMediaRecorder == NULL) {
LOGE("media recorder is not initialized yet");
return INVALID_OPERATION;
@@ -40,7 +40,7 @@
return INVALID_OPERATION;
}
- status_t ret = mMediaRecorder->setCamera(camera);
+ status_t ret = mMediaRecorder->setCamera(camera, proxy);
if (OK != ret) {
LOGV("setCamera failed: %d", ret);
mCurrentState = MEDIA_RECORDER_ERROR;
diff --git a/media/libmediaplayerservice/MediaRecorderClient.cpp b/media/libmediaplayerservice/MediaRecorderClient.cpp
index 29cc019..115db1a 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.cpp
+++ b/media/libmediaplayerservice/MediaRecorderClient.cpp
@@ -57,7 +57,8 @@
return ok;
}
-status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera)
+status_t MediaRecorderClient::setCamera(const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy)
{
LOGV("setCamera");
Mutex::Autolock lock(mLock);
@@ -65,7 +66,7 @@
LOGE("recorder is not initialized");
return NO_INIT;
}
- return mRecorder->setCamera(camera);
+ return mRecorder->setCamera(camera, proxy);
}
status_t MediaRecorderClient::setPreviewSurface(const sp<Surface>& surface)
diff --git a/media/libmediaplayerservice/MediaRecorderClient.h b/media/libmediaplayerservice/MediaRecorderClient.h
index fded98e..bbca529 100644
--- a/media/libmediaplayerservice/MediaRecorderClient.h
+++ b/media/libmediaplayerservice/MediaRecorderClient.h
@@ -24,11 +24,13 @@
class MediaRecorderBase;
class MediaPlayerService;
+class ICameraRecordingProxy;
class MediaRecorderClient : public BnMediaRecorder
{
public:
- virtual status_t setCamera(const sp<ICamera>& camera);
+ virtual status_t setCamera(const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy);
virtual status_t setPreviewSurface(const sp<Surface>& surface);
virtual status_t setVideoSource(int vs);
virtual status_t setAudioSource(int as);
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 978571c..b003476 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -198,14 +198,20 @@
return OK;
}
-status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera) {
+status_t StagefrightRecorder::setCamera(const sp<ICamera> &camera,
+ const sp<ICameraRecordingProxy> &proxy) {
LOGV("setCamera");
if (camera == 0) {
LOGE("camera is NULL");
return BAD_VALUE;
}
+ if (proxy == 0) {
+ LOGE("camera proxy is NULL");
+ return BAD_VALUE;
+ }
mCamera = camera;
+ mCameraProxy = proxy;
return OK;
}
@@ -1235,15 +1241,17 @@
videoSize.height = mVideoHeight;
if (mCaptureTimeLapse) {
mCameraSourceTimeLapse = CameraSourceTimeLapse::CreateFromCamera(
- mCamera, mCameraId,
+ mCamera, mCameraProxy, mCameraId,
videoSize, mFrameRate, mPreviewSurface,
mTimeBetweenTimeLapseFrameCaptureUs);
*cameraSource = mCameraSourceTimeLapse;
} else {
*cameraSource = CameraSource::CreateFromCamera(
- mCamera, mCameraId, videoSize, mFrameRate,
+ mCamera, mCameraProxy, mCameraId, videoSize, mFrameRate,
mPreviewSurface, true /*storeMetaDataInVideoBuffers*/);
}
+ mCamera.clear();
+ mCameraProxy.clear();
if (*cameraSource == NULL) {
return UNKNOWN_ERROR;
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index aa67aa7..cb9c406 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -27,6 +27,7 @@
namespace android {
class Camera;
+class ICameraRecordingProxy;
class CameraSource;
class CameraSourceTimeLapse;
class MediaSourceSplitter;
@@ -48,7 +49,7 @@
virtual status_t setVideoEncoder(video_encoder ve);
virtual status_t setVideoSize(int width, int height);
virtual status_t setVideoFrameRate(int frames_per_second);
- virtual status_t setCamera(const sp<ICamera>& camera);
+ virtual status_t setCamera(const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy);
virtual status_t setPreviewSurface(const sp<Surface>& surface);
virtual status_t setOutputFile(const char *path);
virtual status_t setOutputFile(int fd, int64_t offset, int64_t length);
@@ -66,6 +67,7 @@
private:
sp<ICamera> mCamera;
+ sp<ICameraRecordingProxy> mCameraProxy;
sp<Surface> mPreviewSurface;
sp<IMediaRecorderClient> mListener;
sp<MediaWriter> mWriter, mWriterAux;
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index 61bb2a8..bdffce9 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -115,19 +115,20 @@
size.height = -1;
sp<ICamera> camera;
- return new CameraSource(camera, 0, size, -1, NULL, false);
+ return new CameraSource(camera, NULL, 0, size, -1, NULL, false);
}
// static
CameraSource *CameraSource::CreateFromCamera(
const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId,
Size videoSize,
int32_t frameRate,
const sp<Surface>& surface,
bool storeMetaDataInVideoBuffers) {
- CameraSource *source = new CameraSource(camera, cameraId,
+ CameraSource *source = new CameraSource(camera, proxy, cameraId,
videoSize, frameRate, surface,
storeMetaDataInVideoBuffers);
return source;
@@ -135,6 +136,7 @@
CameraSource::CameraSource(
const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId,
Size videoSize,
int32_t frameRate,
@@ -153,11 +155,10 @@
mNumGlitches(0),
mGlitchDurationThresholdUs(200000),
mCollectStats(false) {
-
mVideoSize.width = -1;
mVideoSize.height = -1;
- mInitCheck = init(camera, cameraId,
+ mInitCheck = init(camera, proxy, cameraId,
videoSize, frameRate,
storeMetaDataInVideoBuffers);
}
@@ -167,24 +168,32 @@
}
status_t CameraSource::isCameraAvailable(
- const sp<ICamera>& camera, int32_t cameraId) {
+ const sp<ICamera>& camera, const sp<ICameraRecordingProxy>& proxy,
+ int32_t cameraId) {
if (camera == 0) {
mCamera = Camera::connect(cameraId);
+ if (mCamera == 0) return -EBUSY;
+ // If proxy is not passed in by applications, still use the proxy of
+ // our own Camera to simplify the code.
+ mCameraRecordingProxy = mCamera->getRecordingProxy();
mCameraFlags &= ~FLAGS_HOT_CAMERA;
} else {
+ // We get the proxy from Camera, not ICamera. We need to get the proxy
+ // to the remote Camera owned by the application. Here mCamera is a
+ // local Camera object created by us. We cannot use the proxy from
+ // mCamera here.
mCamera = Camera::create(camera);
+ if (mCamera == 0) return -EBUSY;
+ mCameraRecordingProxy = proxy;
mCameraFlags |= FLAGS_HOT_CAMERA;
}
- // Is camera available?
- if (mCamera == 0) {
- LOGE("Camera connection could not be established.");
- return -EBUSY;
- }
- if (!(mCameraFlags & FLAGS_HOT_CAMERA)) {
- mCamera->lock();
- }
+ mCamera->lock();
+ mDeathNotifier = new DeathNotifier();
+ // isBinderAlive needs linkToDeath to work.
+ mCameraRecordingProxy->asBinder()->linkToDeath(mDeathNotifier);
+
return OK;
}
@@ -447,6 +456,7 @@
*/
status_t CameraSource::init(
const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId,
Size videoSize,
int32_t frameRate,
@@ -455,7 +465,8 @@
status_t err = OK;
int64_t token = IPCThreadState::self()->clearCallingIdentity();
- if ((err = isCameraAvailable(camera, cameraId)) != OK) {
+ if ((err = isCameraAvailable(camera, proxy, cameraId)) != OK) {
+ LOGE("Camera connection could not be established.");
return err;
}
CameraParameters params(mCamera->getParameters());
@@ -521,8 +532,14 @@
}
void CameraSource::startCameraRecording() {
- CHECK_EQ(OK, mCamera->startRecording());
- CHECK(mCamera->recordingEnabled());
+ // Reset the identity to the current thread because media server owns the
+ // camera and recording is started by the applications. The applications
+ // will connect to the camera in ICameraRecordingProxy::startRecording.
+ int64_t token = IPCThreadState::self()->clearCallingIdentity();
+ mCamera->unlock();
+ mCamera.clear();
+ IPCThreadState::self()->restoreCallingIdentity(token);
+ CHECK_EQ(OK, mCameraRecordingProxy->startRecording(new ProxyListener(this)));
}
status_t CameraSource::start(MetaData *meta) {
@@ -544,20 +561,14 @@
mStartTimeUs = startTimeUs;
}
- // Call setListener first before calling startCameraRecording()
- // to avoid recording frames being dropped.
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
- mCamera->setListener(new CameraSourceListener(this));
startCameraRecording();
- IPCThreadState::self()->restoreCallingIdentity(token);
mStarted = true;
return OK;
}
void CameraSource::stopCameraRecording() {
- mCamera->setListener(NULL);
- mCamera->stopRecording();
+ mCameraRecordingProxy->stopRecording();
}
void CameraSource::releaseCamera() {
@@ -566,9 +577,9 @@
LOGV("Camera was cold when we started, stopping preview");
mCamera->stopPreview();
}
- mCamera->unlock();
mCamera.clear();
- mCamera = 0;
+ mCameraRecordingProxy->asBinder()->unlinkToDeath(mDeathNotifier);
+ mCameraRecordingProxy.clear();
mCameraFlags = 0;
}
@@ -578,7 +589,6 @@
mStarted = false;
mFrameAvailableCondition.signal();
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
releaseQueuedFrames();
while (!mFramesBeingEncoded.empty()) {
if (NO_ERROR !=
@@ -589,7 +599,6 @@
}
stopCameraRecording();
releaseCamera();
- IPCThreadState::self()->restoreCallingIdentity(token);
if (mCollectStats) {
LOGI("Frames received/encoded/dropped: %d/%d/%d in %lld us",
@@ -607,8 +616,8 @@
}
void CameraSource::releaseRecordingFrame(const sp<IMemory>& frame) {
- if (mCamera != NULL) {
- mCamera->releaseRecordingFrame(frame);
+ if (mCameraRecordingProxy != NULL) {
+ mCameraRecordingProxy->releaseRecordingFrame(frame);
}
}
@@ -627,9 +636,7 @@
}
void CameraSource::releaseOneRecordingFrame(const sp<IMemory>& frame) {
- int64_t token = IPCThreadState::self()->clearCallingIdentity();
releaseRecordingFrame(frame);
- IPCThreadState::self()->restoreCallingIdentity(token);
}
void CameraSource::signalBufferReturned(MediaBuffer *buffer) {
@@ -669,7 +676,11 @@
Mutex::Autolock autoLock(mLock);
while (mStarted && mFramesReceived.empty()) {
if (NO_ERROR !=
- mFrameAvailableCondition.waitRelative(mLock, 3000000000LL)) {
+ mFrameAvailableCondition.waitRelative(mLock, 1000000000LL)) {
+ if (!mCameraRecordingProxy->asBinder()->isBinderAlive()) {
+ LOGW("camera recording proxy is gone");
+ return ERROR_END_OF_STREAM;
+ }
LOGW("Timed out waiting for incoming camera video frames: %lld us",
mLastFrameTimestampUs);
}
@@ -745,4 +756,17 @@
return mIsMetaDataStoredInVideoBuffers;
}
+CameraSource::ProxyListener::ProxyListener(const sp<CameraSource>& source) {
+ mSource = source;
+}
+
+void CameraSource::ProxyListener::dataCallbackTimestamp(
+ nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) {
+ mSource->dataCallbackTimestamp(timestamp / 1000, msgType, dataPtr);
+}
+
+void CameraSource::DeathNotifier::binderDied(const wp<IBinder>& who) {
+ LOGI("Camera recording proxy died");
+}
+
} // namespace android
diff --git a/media/libstagefright/CameraSourceTimeLapse.cpp b/media/libstagefright/CameraSourceTimeLapse.cpp
index cc22574..fe78c46 100644
--- a/media/libstagefright/CameraSourceTimeLapse.cpp
+++ b/media/libstagefright/CameraSourceTimeLapse.cpp
@@ -39,6 +39,7 @@
// static
CameraSourceTimeLapse *CameraSourceTimeLapse::CreateFromCamera(
const sp<ICamera> &camera,
+ const sp<ICameraRecordingProxy> &proxy,
int32_t cameraId,
Size videoSize,
int32_t videoFrameRate,
@@ -46,7 +47,7 @@
int64_t timeBetweenTimeLapseFrameCaptureUs) {
CameraSourceTimeLapse *source = new
- CameraSourceTimeLapse(camera, cameraId,
+ CameraSourceTimeLapse(camera, proxy, cameraId,
videoSize, videoFrameRate, surface,
timeBetweenTimeLapseFrameCaptureUs);
@@ -61,12 +62,13 @@
CameraSourceTimeLapse::CameraSourceTimeLapse(
const sp<ICamera>& camera,
+ const sp<ICameraRecordingProxy>& proxy,
int32_t cameraId,
Size videoSize,
int32_t videoFrameRate,
const sp<Surface>& surface,
int64_t timeBetweenTimeLapseFrameCaptureUs)
- : CameraSource(camera, cameraId, videoSize, videoFrameRate, surface, true),
+ : CameraSource(camera, proxy, cameraId, videoSize, videoFrameRate, surface, true),
mTimeBetweenTimeLapseFrameCaptureUs(timeBetweenTimeLapseFrameCaptureUs),
mTimeBetweenTimeLapseVideoFramesUs(1E6/videoFrameRate),
mLastTimeLapseFrameRealTimestampUs(0),
@@ -315,7 +317,7 @@
pthread_attr_destroy(&attr);
} else {
LOGV("start time lapse recording using video camera");
- CHECK_EQ(OK, mCamera->startRecording());
+ CameraSource::startCameraRecording();
}
}
@@ -337,8 +339,7 @@
// play the recording sound.
mCamera->sendCommand(CAMERA_CMD_PLAY_RECORDING_SOUND, 0, 0);
} else {
- mCamera->setListener(NULL);
- mCamera->stopRecording();
+ CameraSource::stopCameraRecording();
}
if (mLastReadBufferCopy) {
mLastReadBufferCopy->release();
@@ -347,9 +348,8 @@
}
void CameraSourceTimeLapse::releaseRecordingFrame(const sp<IMemory>& frame) {
- if (!mUseStillCameraForTimeLapse &&
- mCamera != NULL) {
- mCamera->releaseRecordingFrame(frame);
+ if (!mUseStillCameraForTimeLapse) {
+ CameraSource::releaseRecordingFrame(frame);
}
}
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/codecs/on2/h264dec/SoftAVC.cpp b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
index 830d2e0..ec7bd1c 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.cpp
@@ -139,7 +139,8 @@
}
status_t SoftAVC::initDecoder() {
- if (H264SwDecInit(&mHandle, 1) == H264SWDEC_OK) {
+ // Force decoder to output buffers in display order.
+ if (H264SwDecInit(&mHandle, 0) == H264SWDEC_OK) {
return OK;
}
return UNKNOWN_ERROR;
diff --git a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
index 3439efd..1cc85e8 100644
--- a/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
+++ b/media/libstagefright/codecs/on2/h264dec/SoftAVC.h
@@ -52,7 +52,7 @@
kInputPortIndex = 0,
kOutputPortIndex = 1,
kNumInputBuffers = 8,
- kNumOutputBuffers = 16,
+ kNumOutputBuffers = 2,
};
enum EOSStatus {
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/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/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 0c4ef7d..a9aa31b 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -589,7 +589,12 @@
private void restoreWifiSupplicant(String filename, BackupDataInput data) {
byte[] bytes = new byte[data.getDataSize()];
if (bytes.length <= 0) return;
- restoreWifiSupplicant(filename, bytes, bytes.length);
+ try {
+ data.readEntityData(bytes, 0, data.getDataSize());
+ restoreWifiSupplicant(filename, bytes, bytes.length);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to read supplicant data");
+ }
}
private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
diff --git a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
index cd79b60..6573286 100644
--- a/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/PatternUnlockScreen.java
@@ -183,8 +183,7 @@
mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
// This shows up when no other information is required on status1
- mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions,
- StatusView.LOCK_ICON);
+ //mStatusView.setHelpMessage(R.string.lockscreen_pattern_instructions,StatusView.LOCK_ICON);
mLockPatternView = (LockPatternView) findViewById(R.id.lockPattern);
diff --git a/policy/src/com/android/internal/policy/impl/StatusView.java b/policy/src/com/android/internal/policy/impl/StatusView.java
index 46ce5a3..79f81ff 100644
--- a/policy/src/com/android/internal/policy/impl/StatusView.java
+++ b/policy/src/com/android/internal/policy/impl/StatusView.java
@@ -40,7 +40,7 @@
private String mInstructions = null;
private TextView mStatus1;
- private TextView mPropertyOf;
+ private TextView mOwnerInfo;
private boolean mHasCarrier;
private boolean mHasDate;
@@ -105,7 +105,7 @@
mStatus1 = (TextView) findViewById(R.id.status1);
mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
mAlarmStatus.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0);
- mPropertyOf = (TextView) findViewById(R.id.propertyOf);
+ mOwnerInfo = (TextView) findViewById(R.id.propertyOf);
resetStatusInfo(updateMonitor, lockPatternUtils);
@@ -153,20 +153,22 @@
void updateStatusLines(boolean showStatusLines) {
if (!showStatusLines) {
mStatus1.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE);
- mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.INVISIBLE);
+ mAlarmStatus.setVisibility(showStatusLines ? View.VISIBLE : View.GONE);
return;
}
// Update owner info
- if (mPropertyOf != null) {
- ContentResolver res = getContext().getContentResolver();
- String info = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO);
- boolean enabled = Settings.Secure.getInt(res,
- Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
-
- mPropertyOf.setText(info);
- mPropertyOf.setVisibility(enabled && !TextUtils.isEmpty(info) ?
- View.VISIBLE : View.INVISIBLE);
+ final ContentResolver res = getContext().getContentResolver();
+ final boolean ownerInfoEnabled = Settings.Secure.getInt(res,
+ Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0;
+ String ownerInfo = null;
+ if (ownerInfoEnabled) {
+ ownerInfo = Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO);
+ if (mOwnerInfo != null) {
+ mOwnerInfo.setText(ownerInfo);
+ mOwnerInfo.setVisibility(ownerInfoEnabled && !TextUtils.isEmpty(ownerInfo) ?
+ View.VISIBLE : View.INVISIBLE);
+ }
}
// Update Alarm status
@@ -175,7 +177,7 @@
mAlarmStatus.setText(nextAlarm);
mAlarmStatus.setVisibility(View.VISIBLE);
} else {
- mAlarmStatus.setVisibility(View.INVISIBLE);
+ mAlarmStatus.setVisibility(View.GONE);
}
// Update Status1
@@ -204,15 +206,16 @@
} else {
mStatus1.setVisibility(View.INVISIBLE);
}
+ } else if (mHelpMessageId != 0) {
+ mStatus1.setText(mHelpMessageId);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0);
+ mStatus1.setVisibility(View.VISIBLE);
+ } else if (ownerInfoEnabled && mOwnerInfo == null && ownerInfo != null) {
+ mStatus1.setText(ownerInfo);
+ mStatus1.setCompoundDrawablesWithIntrinsicBounds(0, 0,0, 0);
+ mStatus1.setVisibility(View.VISIBLE);
} else {
- // nothing specific to show; show help message and icon, if provided
- if (mHelpMessageId != 0) {
- mStatus1.setText(mHelpMessageId);
- mStatus1.setCompoundDrawablesWithIntrinsicBounds(mHelpIconId, 0,0, 0);
- mStatus1.setVisibility(View.VISIBLE);
- } else {
- mStatus1.setVisibility(View.INVISIBLE);
- }
+ mStatus1.setVisibility(View.INVISIBLE);
}
}
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index a011ae2..9b09983 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -420,6 +420,10 @@
// allow anyone to use camera (after they lock the camera)
status_t result = checkPid();
if (result == NO_ERROR) {
+ if (mHardware->recordingEnabled()) {
+ LOGE("Not allowed to unlock camera during recording.");
+ return INVALID_OPERATION;
+ }
mClientPid = 0;
LOG1("clear mCameraClient (pid %d)", callingPid);
// we need to remove the reference to ICameraClient so that when the app
@@ -756,6 +760,11 @@
status_t result = checkPidAndHardware();
if (result != NO_ERROR) return result;
+ if (mHardware->recordingEnabled()) {
+ LOGE("Cannot take picture during recording.");
+ return INVALID_OPERATION;
+ }
+
if ((msgType & CAMERA_MSG_RAW_IMAGE) &&
(msgType & CAMERA_MSG_RAW_IMAGE_NOTIFY)) {
LOGE("CAMERA_MSG_RAW_IMAGE and CAMERA_MSG_RAW_IMAGE_NOTIFY"
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index e6f443a..8ac8f2b 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -131,8 +131,6 @@
*/
private List mNetRequestersPids[];
- private WifiWatchdogService mWifiWatchdogService;
-
// priority order of the nettrackers
// (excluding dynamically set mNetworkPreference)
// TODO - move mNetworkTypePreference into this
@@ -278,6 +276,9 @@
}
RadioAttributes[] mRadioAttributes;
+ // the set of network types that can only be enabled by system/sig apps
+ List mProtectedNetworks;
+
public ConnectivityService(
Context context, INetworkManagementService netd, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
@@ -381,6 +382,17 @@
}
}
+ mProtectedNetworks = new ArrayList<Integer>();
+ int[] protectedNetworks = context.getResources().getIntArray(
+ com.android.internal.R.array.config_protectedNetworks);
+ for (int p : protectedNetworks) {
+ if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+ mProtectedNetworks.add(p);
+ } else {
+ if (DBG) loge("Ignoring protectedNetwork " + p);
+ }
+ }
+
// high priority first
mPriorityList = new int[mNetworksDefined];
{
@@ -432,10 +444,6 @@
wifiService.checkAndStartWifi();
mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst;
wst.startMonitoring(context, mHandler);
-
- //TODO: as part of WWS refactor, create only when needed
- mWifiWatchdogService = new WifiWatchdogService(context);
-
break;
case ConnectivityManager.TYPE_MOBILE:
mNetTrackers[netType] = new MobileDataStateTracker(netType,
@@ -802,6 +810,11 @@
usedNetworkType = networkType;
}
}
+
+ if (mProtectedNetworks.contains(usedNetworkType)) {
+ enforceConnectivityInternalPermission();
+ }
+
NetworkStateTracker network = mNetTrackers[usedNetworkType];
if (network != null) {
Integer currentPid = new Integer(getCallingPid());
@@ -1012,6 +1025,10 @@
*/
public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
enforceChangePermission();
+ if (mProtectedNetworks.contains(networkType)) {
+ enforceConnectivityInternalPermission();
+ }
+
if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
return false;
}
@@ -1129,7 +1146,8 @@
}
public void setDataDependency(int networkType, boolean met) {
- enforceChangePermission();
+ enforceConnectivityInternalPermission();
+
if (DBG) {
log("setDataDependency(" + networkType + ", " + met + ")");
}
diff --git a/services/java/com/android/server/DnsPinger.java b/services/java/com/android/server/DnsPinger.java
new file mode 100644
index 0000000..5cfda7e
--- /dev/null
+++ b/services/java/com/android/server/DnsPinger.java
@@ -0,0 +1,188 @@
+/*
+ * 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.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.util.Collection;
+import java.util.Random;
+
+/**
+ * Performs a simple DNS "ping" by sending a "server status" query packet to the
+ * DNS server. As long as the server replies, we consider it a success.
+ * <p>
+ * We do not use a simple hostname lookup because that could be cached and the
+ * API may not differentiate between a time out and a failure lookup (which we
+ * really care about).
+ * <p>
+ * TODO : More general API. Wifi is currently hard coded
+ * TODO : Choice of DNS query location - current looks up www.android.com
+ *
+ * @hide
+ */
+public final class DnsPinger {
+ private static final boolean V = true;
+
+ /** Number of bytes for the query */
+ private static final int DNS_QUERY_BASE_SIZE = 33;
+
+ /** The DNS port */
+ private static final int DNS_PORT = 53;
+
+ /** Used to generate IDs */
+ private static Random sRandom = new Random();
+
+ private ConnectivityManager mConnectivityManager = null;
+ private ContentResolver mContentResolver;
+ private Context mContext;
+
+ private String TAG;
+
+ public DnsPinger(String TAG, Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ this.TAG = TAG;
+ }
+
+ /**
+ * Gets the first DNS of the current Wifi AP.
+ * @return The first DNS of the current AP.
+ */
+ public InetAddress getDns() {
+ if (mConnectivityManager == null) {
+ mConnectivityManager = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ }
+
+ LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
+ ConnectivityManager.TYPE_WIFI);
+ if (linkProperties == null)
+ return null;
+
+ Collection<InetAddress> dnses = linkProperties.getDnses();
+ if (dnses == null || dnses.size() == 0)
+ return null;
+
+ return dnses.iterator().next();
+ }
+
+ /**
+ * @return time to response. Negative value on error.
+ */
+ public long pingDns(InetAddress dnsAddress, int timeout) {
+ DatagramSocket socket = null;
+ try {
+ socket = new DatagramSocket();
+
+ // Set some socket properties
+ socket.setSoTimeout(timeout);
+
+ byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
+ fillQuery(buf);
+
+ // Send the DNS query
+
+ DatagramPacket packet = new DatagramPacket(buf,
+ buf.length, dnsAddress, DNS_PORT);
+ long start = SystemClock.elapsedRealtime();
+ socket.send(packet);
+
+ // Wait for reply (blocks for the above timeout)
+ DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
+ socket.receive(replyPacket);
+
+ // If a timeout occurred, an exception would have been thrown. We
+ // got a reply!
+ return SystemClock.elapsedRealtime() - start;
+
+ } catch (SocketTimeoutException e) {
+ // Squelch this exception.
+ return -1;
+ } catch (Exception e) {
+ if (V) {
+ Slog.v(TAG, "DnsPinger.pingDns got socket exception: ", e);
+ }
+ return -2;
+ } finally {
+ if (socket != null) {
+ socket.close();
+ }
+ }
+
+ }
+
+ private static void fillQuery(byte[] buf) {
+
+ /*
+ * See RFC2929 (though the bit tables in there are misleading for us.
+ * For example, the recursion desired bit is the 0th bit for us, but
+ * looking there it would appear as the 7th bit of the byte
+ */
+
+ // Make sure it's all zeroed out
+ for (int i = 0; i < buf.length; i++)
+ buf[i] = 0;
+
+ // Form a query for www.android.com
+
+ // [0-1] bytes are an ID, generate random ID for this query
+ buf[0] = (byte) sRandom.nextInt(256);
+ buf[1] = (byte) sRandom.nextInt(256);
+
+ // [2-3] bytes are for flags.
+ buf[2] = 1; // Recursion desired
+
+ // [4-5] bytes are for the query count
+ buf[5] = 1; // One query
+
+ // [6-7] [8-9] [10-11] are all counts of other fields we don't use
+
+ // [12-15] for www
+ writeString(buf, 12, "www");
+
+ // [16-23] for android
+ writeString(buf, 16, "android");
+
+ // [24-27] for com
+ writeString(buf, 24, "com");
+
+ // [29-30] bytes are for QTYPE, set to 1
+ buf[30] = 1;
+
+ // [31-32] bytes are for QCLASS, set to 1
+ buf[32] = 1;
+ }
+
+ private static void writeString(byte[] buf, int startPos, String string) {
+ int pos = startPos;
+
+ // Write the length first
+ buf[pos++] = (byte) string.length();
+ for (int i = 0; i < string.length(); i++) {
+ buf[pos++] = (byte) string.charAt(i);
+ }
+ }
+}
diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java
index cb55451..7725891 100644
--- a/services/java/com/android/server/WifiService.java
+++ b/services/java/com/android/server/WifiService.java
@@ -342,6 +342,7 @@
* Protected by mWifiStateTracker lock.
*/
private final WorkSource mTmpWorkSource = new WorkSource();
+ private WifiWatchdogService mWifiWatchdogService;
WifiService(Context context) {
mContext = context;
@@ -431,6 +432,9 @@
Slog.i(TAG, "WifiService starting up with Wi-Fi " +
(wifiEnabled ? "enabled" : "disabled"));
setWifiEnabled(wifiEnabled);
+
+ //TODO: as part of WWS refactor, create only when needed
+ mWifiWatchdogService = new WifiWatchdogService(mContext);
}
private boolean testAndClearWifiSavedState() {
@@ -1155,6 +1159,10 @@
pw.println();
pw.println("Locks held:");
mLocks.dump(pw);
+
+ pw.println();
+ pw.println("WifiWatchdogService dump");
+ mWifiWatchdogService.dump(pw);
}
private class WifiLock extends DeathRecipient {
diff --git a/services/java/com/android/server/WifiWatchdogService.java b/services/java/com/android/server/WifiWatchdogService.java
index 56bfbe0..0b79478 100644
--- a/services/java/com/android/server/WifiWatchdogService.java
+++ b/services/java/com/android/server/WifiWatchdogService.java
@@ -22,1429 +22,743 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
-import android.net.ConnectivityManager;
-import android.net.LinkProperties;
import android.net.NetworkInfo;
+import android.net.Uri;
import android.net.wifi.ScanResult;
+import android.net.wifi.SupplicantState;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import java.io.BufferedInputStream;
-import java.io.InputStream;
import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
+import java.io.InputStream;
+import java.io.PrintWriter;
import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.net.SocketTimeoutException;
-import java.net.UnknownHostException;
import java.net.URL;
-import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
-import java.util.Random;
import java.util.Scanner;
/**
* {@link WifiWatchdogService} monitors the initial connection to a Wi-Fi
* network with multiple access points. After the framework successfully
- * connects to an access point, the watchdog verifies whether the DNS server is
- * reachable. If not, the watchdog blacklists the current access point, leading
- * to a connection on another access point within the same network.
+ * connects to an access point, the watchdog verifies connectivity by 'pinging'
+ * the configured DNS server using {@link DnsPinger}.
* <p>
- * The watchdog has a few safeguards:
- * <ul>
- * <li>Only monitor networks with multiple access points
- * <li>Only check at most {@link #getMaxApChecks()} different access points
- * within the network before giving up
+ * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
+ * that another AP might have internet access; otherwise the SSID is disabled.
* <p>
- * The watchdog checks for connectivity on an access point by ICMP pinging the
- * DNS. There are settings that allow disabling the watchdog, or tweaking the
- * acceptable packet loss (and other various parameters).
- * <p>
- * The core logic of the watchdog is done on the main watchdog thread. Wi-Fi
- * callbacks can come in on other threads, so we must queue messages to the main
- * watchdog thread's handler. Most (if not all) state is only written to from
- * the main thread.
+ * On DNS success, the WatchdogService initiates a walled garden check via an
+ * http get. A browser windows is activated if a walled garden is detected.
*
- * {@hide}
+ * @hide
*/
public class WifiWatchdogService {
- private static final String TAG = "WifiWatchdogService";
- private static final boolean V = false;
- private static final boolean D = true;
+
+ private static final String WWS_TAG = "WifiWatchdogService";
+
+ private static final boolean VDBG = true;
+ private static final boolean DBG = true;
+
+ // Used for verbose logging
+ private String mDNSCheckLogStr;
private Context mContext;
private ContentResolver mContentResolver;
private WifiManager mWifiManager;
- private ConnectivityManager mConnectivityManager;
- /**
- * The main watchdog thread.
- */
- private WifiWatchdogThread mThread;
- /**
- * The handler for the main watchdog thread.
- */
private WifiWatchdogHandler mHandler;
- private ContentObserver mContentObserver;
+ private DnsPinger mDnsPinger;
+
+ private IntentFilter mIntentFilter;
+ private BroadcastReceiver mBroadcastReceiver;
+ private boolean mBroadcastsEnabled;
+
+ private static final int WIFI_SIGNAL_LEVELS = 4;
/**
- * The current watchdog state. Only written from the main thread!
+ * Low signal is defined as less than or equal to cut off
*/
- private WatchdogState mState = WatchdogState.IDLE;
- /**
- * The SSID of the network that the watchdog is currently monitoring. Only
- * touched in the main thread!
- */
- private String mSsid;
- /**
- * The number of access points in the current network ({@link #mSsid}) that
- * have been checked. Only touched in the main thread, using getter/setter methods.
- */
- private int mBssidCheckCount;
- /** Whether the current AP check should be canceled. */
- private boolean mShouldCancel;
+ private static final int LOW_SIGNAL_CUTOFF = 0;
+
+ private static final long MIN_LOW_SIGNAL_CHECK_INTERVAL = 2 * 60 * 1000;
+ private static final long MIN_SINGLE_DNS_CHECK_INTERVAL = 10 * 60 * 1000;
+ private static final long MIN_WALLED_GARDEN_INTERVAL = 15 * 60 * 1000;
+
+ private static final int MAX_CHECKS_PER_SSID = 7;
+ private static final int NUM_DNS_PINGS = 5;
+ private static double MIN_RESPONSE_RATE = 0.50;
+
+ // TODO : Adjust multiple DNS downward to 250 on repeated failure
+ // private static final int MULTI_DNS_PING_TIMEOUT_MS = 250;
+
+ private static final int DNS_PING_TIMEOUT_MS = 1000;
+ private static final long DNS_PING_INTERVAL = 250;
+
+ private static final long BLACKLIST_FOLLOWUP_INTERVAL = 15 * 1000;
+
+ private Status mStatus = new Status();
+
+ private static class Status {
+ String bssid = "";
+ String ssid = "";
+
+ HashSet<String> allBssids = new HashSet<String>();
+ int numFullDNSchecks = 0;
+
+ long lastSingleCheckTime = -24 * 60 * 60 * 1000;
+ long lastWalledGardenCheckTime = -24 * 60 * 60 * 1000;
+
+ WatchdogState state = WatchdogState.INACTIVE;
+
+ // Info for dns check
+ int dnsCheckTries = 0;
+ int dnsCheckSuccesses = 0;
+
+ public int signal = -200;
+
+ }
+
+ private enum WatchdogState {
+ /**
+ * Full DNS check in progress
+ */
+ DNS_FULL_CHECK,
+
+ /**
+ * Walled Garden detected, will pop up browser next round.
+ */
+ WALLED_GARDEN_DETECTED,
+
+ /**
+ * DNS failed, will blacklist/disable AP next round
+ */
+ DNS_CHECK_FAILURE,
+
+ /**
+ * Online or displaying walled garden auth page
+ */
+ CHECKS_COMPLETE,
+
+ /**
+ * Watchdog idle, network has been blacklisted or received disconnect
+ * msg
+ */
+ INACTIVE,
+
+ BLACKLISTED_AP
+ }
WifiWatchdogService(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
- createThread();
-
- // The content observer to listen needs a handler, which createThread creates
+ mDnsPinger = new DnsPinger("WifiWatchdogServer.DnsPinger", context);
+
+ HandlerThread handlerThread = new HandlerThread("WifiWatchdogServiceThread");
+ handlerThread.start();
+ mHandler = new WifiWatchdogHandler(handlerThread.getLooper());
+
+ setupNetworkReceiver();
+
+ // The content observer to listen needs a handler, which createThread
+ // creates
registerForSettingsChanges();
+
+ // Start things off
if (isWatchdogEnabled()) {
- registerForWifiBroadcasts();
+ mHandler.sendEmptyMessage(WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT);
}
-
- if (V) {
- myLogV("WifiWatchdogService: Created");
- }
+ }
+
+ /**
+ *
+ */
+ private void setupNetworkReceiver() {
+ mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ WifiWatchdogHandler.MESSAGE_NETWORK_EVENT,
+ intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO)
+ ));
+ } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
+ mHandler.sendEmptyMessage(WifiWatchdogHandler.RSSI_CHANGE_EVENT);
+ } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+ mHandler.sendEmptyMessage(WifiWatchdogHandler.SCAN_RESULTS_AVAILABLE);
+ } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ WifiWatchdogHandler.WIFI_STATE_CHANGE,
+ intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 4)));
+ }
+ }
+ };
+
+ mIntentFilter = new IntentFilter();
+ mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
+ mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
}
/**
* Observes the watchdog on/off setting, and takes action when changed.
*/
private void registerForSettingsChanges() {
- ContentResolver contentResolver = mContext.getContentResolver();
- contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON), false,
- mContentObserver = new ContentObserver(mHandler) {
+ ContentObserver contentObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- if (isWatchdogEnabled()) {
- registerForWifiBroadcasts();
- } else {
- unregisterForWifiBroadcasts();
- if (mHandler != null) {
- mHandler.disableWatchdog();
+ mHandler.sendEmptyMessage((WifiWatchdogHandler.MESSAGE_CONTEXT_EVENT));
+ }
+ };
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
+ false, contentObserver);
+ }
+
+ private void handleNewConnection() {
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ String newSsid = wifiInfo.getSSID();
+ String newBssid = wifiInfo.getBSSID();
+
+ if (VDBG) {
+ Slog.v(WWS_TAG, String.format("handleConnected:: old (%s, %s) ==> new (%s, %s)",
+ mStatus.ssid, mStatus.bssid, newSsid, newBssid));
+ }
+
+ if (TextUtils.isEmpty(newSsid) || TextUtils.isEmpty(newBssid)) {
+ return;
+ }
+
+ if (!TextUtils.equals(mStatus.ssid, newSsid)) {
+ mStatus = new Status();
+ mStatus.ssid = newSsid;
+ }
+
+ mStatus.bssid = newBssid;
+ mStatus.allBssids.add(newBssid);
+ mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+
+ initDnsFullCheck();
+ }
+
+ public void updateRssi() {
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ if (!TextUtils.equals(mStatus.ssid, wifiInfo.getSSID()) ||
+ !TextUtils.equals(mStatus.bssid, wifiInfo.getBSSID())) {
+ return;
+ }
+
+ mStatus.signal = WifiManager.calculateSignalLevel(wifiInfo.getRssi(), WIFI_SIGNAL_LEVELS);
+ }
+
+ /**
+ * Single step in state machine
+ */
+ private void handleStateStep() {
+ // Slog.v(WWS_TAG, "handleStateStep:: " + mStatus.state);
+
+ switch (mStatus.state) {
+ case DNS_FULL_CHECK:
+ if (VDBG) {
+ Slog.v(WWS_TAG, "DNS_FULL_CHECK: " + mDNSCheckLogStr);
+ }
+
+ long pingResponseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+ DNS_PING_TIMEOUT_MS);
+
+ mStatus.dnsCheckTries++;
+ if (pingResponseTime >= 0)
+ mStatus.dnsCheckSuccesses++;
+
+ if (DBG) {
+ if (pingResponseTime >= 0) {
+ mDNSCheckLogStr += " | " + pingResponseTime;
+ } else {
+ mDNSCheckLogStr += " | " + "x";
}
}
+
+ switch (currentDnsCheckStatus()) {
+ case SUCCESS:
+ if (DBG) {
+ Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Success");
+ }
+ doWalledGardenCheck();
+ break;
+ case FAILURE:
+ if (DBG) {
+ Slog.d(WWS_TAG, mDNSCheckLogStr + " -- Failure");
+ }
+ mStatus.state = WatchdogState.DNS_CHECK_FAILURE;
+ break;
+ case INCOMPLETE:
+ // Taking no action
+ break;
+ }
+ break;
+ case DNS_CHECK_FAILURE:
+ WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+ if (!mStatus.ssid.equals(wifiInfo.getSSID()) ||
+ !mStatus.bssid.equals(wifiInfo.getBSSID())) {
+ Slog.i(WWS_TAG, "handleState DNS_CHECK_FAILURE:: network has changed!");
+ mStatus.state = WatchdogState.INACTIVE;
+ break;
+ }
+
+ if (mStatus.numFullDNSchecks >= mStatus.allBssids.size() ||
+ mStatus.numFullDNSchecks >= MAX_CHECKS_PER_SSID) {
+ disableAP(wifiInfo);
+ } else {
+ blacklistAP();
+ }
+ break;
+ case WALLED_GARDEN_DETECTED:
+ popUpBrowser();
+ mStatus.state = WatchdogState.CHECKS_COMPLETE;
+ break;
+ case BLACKLISTED_AP:
+ WifiInfo wifiInfo2 = mWifiManager.getConnectionInfo();
+ if (wifiInfo2.getSupplicantState() != SupplicantState.COMPLETED) {
+ Slog.d(WWS_TAG,
+ "handleState::BlacklistedAP - offline, but didn't get disconnect!");
+ mStatus.state = WatchdogState.INACTIVE;
+ break;
+ }
+ if (mStatus.bssid.equals(wifiInfo2.getBSSID())) {
+ Slog.d(WWS_TAG, "handleState::BlacklistedAP - connected to same bssid");
+ if (!handleSingleDnsCheck()) {
+ disableAP(wifiInfo2);
+ break;
+ }
+ }
+
+ Slog.d(WWS_TAG, "handleState::BlacklistedAP - Simiulating a new connection");
+ handleNewConnection();
+ break;
+ }
+ }
+
+ private void doWalledGardenCheck() {
+ if (!isWalledGardenTestEnabled()) {
+ if (VDBG)
+ Slog.v(WWS_TAG, "Skipping walled garden check - disabled");
+ mStatus.state = WatchdogState.CHECKS_COMPLETE;
+ return;
+ }
+ long waitTime = waitTime(MIN_WALLED_GARDEN_INTERVAL,
+ mStatus.lastWalledGardenCheckTime);
+ if (waitTime > 0) {
+ if (VDBG) {
+ Slog.v(WWS_TAG, "Skipping walled garden check - wait " +
+ waitTime + " ms.");
}
- });
+ mStatus.state = WatchdogState.CHECKS_COMPLETE;
+ return;
+ }
+
+ mStatus.lastWalledGardenCheckTime = SystemClock.elapsedRealtime();
+ if (isWalledGardenConnection()) {
+ if (DBG)
+ Slog.d(WWS_TAG,
+ "Walled garden test complete - walled garden detected");
+ mStatus.state = WatchdogState.WALLED_GARDEN_DETECTED;
+ } else {
+ if (DBG)
+ Slog.d(WWS_TAG, "Walled garden test complete - online");
+ mStatus.state = WatchdogState.CHECKS_COMPLETE;
+ }
+ }
+
+ private boolean handleSingleDnsCheck() {
+ mStatus.lastSingleCheckTime = SystemClock.elapsedRealtime();
+ long responseTime = mDnsPinger.pingDns(mDnsPinger.getDns(),
+ DNS_PING_TIMEOUT_MS);
+ if (DBG) {
+ Slog.d(WWS_TAG, "Ran a single DNS ping. Response time: " + responseTime);
+ }
+ if (responseTime < 0) {
+ return false;
+ }
+ return true;
+
+ }
+
+ /**
+ * @return Delay in MS before next single DNS check can proceed.
+ */
+ private long timeToNextScheduledDNSCheck() {
+ if (mStatus.signal > LOW_SIGNAL_CUTOFF) {
+ return waitTime(MIN_SINGLE_DNS_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+ } else {
+ return waitTime(MIN_LOW_SIGNAL_CHECK_INTERVAL, mStatus.lastSingleCheckTime);
+ }
+ }
+
+ /**
+ * Helper to return wait time left given a min interval and last run
+ *
+ * @param interval minimum wait interval
+ * @param lastTime last time action was performed in
+ * SystemClock.elapsedRealtime()
+ * @return non negative time to wait
+ */
+ private static long waitTime(long interval, long lastTime) {
+ long wait = interval + lastTime - SystemClock.elapsedRealtime();
+ return wait > 0 ? wait : 0;
+ }
+
+ private void popUpBrowser() {
+ Uri uri = Uri.parse("http://www.google.com");
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ }
+
+ private void disableAP(WifiInfo info) {
+ // TODO : Unban networks if they had low signal ?
+ Slog.i(WWS_TAG, String.format("Disabling current SSID, %s [bssid %s]. " +
+ "numChecks %d, numAPs %d", mStatus.ssid, mStatus.bssid,
+ mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+ mWifiManager.disableNetwork(info.getNetworkId());
+ mStatus.state = WatchdogState.INACTIVE;
+ }
+
+ private void blacklistAP() {
+ Slog.i(WWS_TAG, String.format("Blacklisting current BSSID %s [ssid %s]. " +
+ "numChecks %d, numAPs %d", mStatus.bssid, mStatus.ssid,
+ mStatus.numFullDNSchecks, mStatus.allBssids.size()));
+
+ mWifiManager.addToBlacklist(mStatus.bssid);
+ mWifiManager.reassociate();
+ mStatus.state = WatchdogState.BLACKLISTED_AP;
+ }
+
+ /**
+ * Checks the scan for new BBIDs using current mSsid
+ */
+ private void updateBssids() {
+ String curSsid = mStatus.ssid;
+ HashSet<String> bssids = mStatus.allBssids;
+ List<ScanResult> results = mWifiManager.getScanResults();
+ int oldNumBssids = bssids.size();
+
+ if (results == null) {
+ if (VDBG) {
+ Slog.v(WWS_TAG, "updateBssids: Got null scan results!");
+ }
+ return;
+ }
+
+ for (ScanResult result : results) {
+ if (result != null && curSsid.equals(result.SSID))
+ bssids.add(result.BSSID);
+ }
+
+ // if (VDBG && bssids.size() - oldNumBssids > 0) {
+ // Slog.v(WWS_TAG,
+ // String.format("updateBssids:: Found %d new APs (total %d) on SSID %s",
+ // bssids.size() - oldNumBssids, bssids.size(), curSsid));
+ // }
+ }
+
+ enum DnsCheckStatus {
+ SUCCESS,
+ FAILURE,
+ INCOMPLETE
+ }
+
+ /**
+ * Computes the current results of the dns check, ends early if outcome is
+ * assured.
+ */
+ private DnsCheckStatus currentDnsCheckStatus() {
+ /**
+ * After a full ping count, if we have more responses than this cutoff,
+ * the outcome is success; else it is 'failure'.
+ */
+ double pingResponseCutoff = MIN_RESPONSE_RATE * NUM_DNS_PINGS;
+ int remainingChecks = NUM_DNS_PINGS - mStatus.dnsCheckTries;
+
+ /**
+ * Our final success count will be at least this big, so we're
+ * guaranteed to succeed.
+ */
+ if (mStatus.dnsCheckSuccesses >= pingResponseCutoff) {
+ return DnsCheckStatus.SUCCESS;
+ }
+
+ /**
+ * Our final count will be at most the current count plus the remaining
+ * pings - we're guaranteed to fail.
+ */
+ if (remainingChecks + mStatus.dnsCheckSuccesses < pingResponseCutoff) {
+ return DnsCheckStatus.FAILURE;
+ }
+
+ return DnsCheckStatus.INCOMPLETE;
+ }
+
+ private void initDnsFullCheck() {
+ if (DBG) {
+ Slog.d(WWS_TAG, "Starting DNS pings at " + SystemClock.elapsedRealtime());
+ }
+ mStatus.numFullDNSchecks++;
+ mStatus.dnsCheckSuccesses = 0;
+ mStatus.dnsCheckTries = 0;
+ mStatus.state = WatchdogState.DNS_FULL_CHECK;
+
+ if (DBG) {
+ mDNSCheckLogStr = String.format("Dns Check %d. Pinging %s on ssid [%s]: ",
+ mStatus.numFullDNSchecks, mDnsPinger.getDns().getHostAddress(),
+ mStatus.ssid);
+ }
+ }
+
+ /**
+ * DNS based detection techniques do not work at all hotspots. The one sure
+ * way to check a walled garden is to see if a URL fetch on a known address
+ * fetches the data we expect
+ */
+ private boolean isWalledGardenConnection() {
+ InputStream in = null;
+ HttpURLConnection urlConnection = null;
+ try {
+ URL url = new URL(getWalledGardenUrl());
+ urlConnection = (HttpURLConnection) url.openConnection();
+ in = new BufferedInputStream(urlConnection.getInputStream());
+ Scanner scanner = new Scanner(in);
+ if (scanner.findInLine(getWalledGardenPattern()) != null) {
+ return false;
+ } else {
+ return true;
+ }
+ } catch (IOException e) {
+ return false;
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ if (urlConnection != null)
+ urlConnection.disconnect();
+ }
+ }
+
+ /**
+ * There is little logic inside this class, instead methods of the form
+ * "handle___" are called in the main {@link WifiWatchdogService}.
+ */
+ private class WifiWatchdogHandler extends Handler {
+ /**
+ * Major network event, object is NetworkInfo
+ */
+ static final int MESSAGE_NETWORK_EVENT = 1;
+ /**
+ * Change in settings, no object
+ */
+ static final int MESSAGE_CONTEXT_EVENT = 2;
+
+ /**
+ * Change in signal strength
+ */
+ static final int RSSI_CHANGE_EVENT = 3;
+ static final int SCAN_RESULTS_AVAILABLE = 4;
+
+ static final int WIFI_STATE_CHANGE = 5;
+
+ /**
+ * Single step of state machine. One DNS check, or one WalledGarden
+ * check, or one external action. We separate out external actions to
+ * increase chance of detecting that a check failure is caused by change
+ * in network status. Messages should have an arg1 which to sync status
+ * messages.
+ */
+ static final int CHECK_SEQUENCE_STEP = 10;
+ static final int SINGLE_DNS_CHECK = 11;
+
+ /**
+ * @param looper
+ */
+ public WifiWatchdogHandler(Looper looper) {
+ super(looper);
+ }
+
+ boolean singleCheckQueued = false;
+ long queuedSingleDnsCheckArrival;
+
+ /**
+ * Sends a singleDnsCheck message with shortest time - guards against
+ * multiple.
+ */
+ private boolean queueSingleDnsCheck() {
+ long delay = timeToNextScheduledDNSCheck();
+ long newArrival = delay + SystemClock.elapsedRealtime();
+ if (singleCheckQueued && queuedSingleDnsCheckArrival <= newArrival)
+ return true;
+ queuedSingleDnsCheckArrival = newArrival;
+ singleCheckQueued = true;
+ removeMessages(SINGLE_DNS_CHECK);
+ return sendMessageDelayed(obtainMessage(SINGLE_DNS_CHECK), delay);
+ }
+
+ boolean checkSequenceQueued = false;
+ long queuedCheckSequenceArrival;
+
+ /**
+ * Sends a state_machine_step message if the delay requested is lower
+ * than the current delay.
+ */
+ private boolean sendCheckSequenceStep(long delay) {
+ long newArrival = delay + SystemClock.elapsedRealtime();
+ if (checkSequenceQueued && queuedCheckSequenceArrival <= newArrival)
+ return true;
+ queuedCheckSequenceArrival = newArrival;
+ checkSequenceQueued = true;
+ removeMessages(CHECK_SEQUENCE_STEP);
+ return sendMessageDelayed(obtainMessage(CHECK_SEQUENCE_STEP), delay);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case CHECK_SEQUENCE_STEP:
+ checkSequenceQueued = false;
+ handleStateStep();
+ if (mStatus.state == WatchdogState.CHECKS_COMPLETE) {
+ queueSingleDnsCheck();
+ } else if (mStatus.state == WatchdogState.DNS_FULL_CHECK) {
+ sendCheckSequenceStep(DNS_PING_INTERVAL);
+ } else if (mStatus.state == WatchdogState.BLACKLISTED_AP) {
+ sendCheckSequenceStep(BLACKLIST_FOLLOWUP_INTERVAL);
+ } else if (mStatus.state != WatchdogState.INACTIVE) {
+ sendCheckSequenceStep(0);
+ }
+ return;
+ case MESSAGE_NETWORK_EVENT:
+ if (!mBroadcastsEnabled) {
+ Slog.e(WWS_TAG,
+ "MessageNetworkEvent - WatchdogService not enabled... returning");
+ return;
+ }
+ NetworkInfo info = (NetworkInfo) msg.obj;
+ switch (info.getState()) {
+ case DISCONNECTED:
+ mStatus.state = WatchdogState.INACTIVE;
+ return;
+ case CONNECTED:
+ handleNewConnection();
+ sendCheckSequenceStep(0);
+ }
+ return;
+ case SINGLE_DNS_CHECK:
+ singleCheckQueued = false;
+ if (mStatus.state != WatchdogState.CHECKS_COMPLETE) {
+ Slog.d(WWS_TAG, "Single check returning, curState: " + mStatus.state);
+ break;
+ }
+
+ if (!handleSingleDnsCheck()) {
+ initDnsFullCheck();
+ sendCheckSequenceStep(0);
+ } else {
+ queueSingleDnsCheck();
+ }
+
+ break;
+ case RSSI_CHANGE_EVENT:
+ updateRssi();
+ if (mStatus.state == WatchdogState.CHECKS_COMPLETE)
+ queueSingleDnsCheck();
+ break;
+ case SCAN_RESULTS_AVAILABLE:
+ updateBssids();
+ break;
+ case WIFI_STATE_CHANGE:
+ if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
+ Slog.i(WWS_TAG, "WifiStateDisabling -- Resetting WatchdogState");
+ mStatus = new Status();
+ }
+ break;
+ case MESSAGE_CONTEXT_EVENT:
+ if (isWatchdogEnabled() && !mBroadcastsEnabled) {
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
+ mBroadcastsEnabled = true;
+ Slog.i(WWS_TAG, "WifiWatchdogService enabled");
+ } else if (!isWatchdogEnabled() && mBroadcastsEnabled) {
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ removeMessages(SINGLE_DNS_CHECK);
+ removeMessages(CHECK_SEQUENCE_STEP);
+ mBroadcastsEnabled = false;
+ Slog.i(WWS_TAG, "WifiWatchdogService disabled");
+ }
+ break;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.print("WatchdogStatus: ");
+ pw.print("State " + mStatus.state);
+ pw.println(", network [" + mStatus.ssid + ", " + mStatus.bssid + "]");
+ pw.print("checkCount " + mStatus.numFullDNSchecks);
+ pw.print(", bssids: " + mStatus.allBssids.size());
+ pw.print(", hasCheckMessages? " +
+ mHandler.hasMessages(WifiWatchdogHandler.CHECK_SEQUENCE_STEP));
+ pw.println(" hasSingleCheckMessages? " +
+ mHandler.hasMessages(WifiWatchdogHandler.SINGLE_DNS_CHECK));
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
+ */
+ private Boolean isWalledGardenTestEnabled() {
+ return Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
+ */
+ private String getWalledGardenUrl() {
+ String url = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
+ if (TextUtils.isEmpty(url))
+ return "http://www.google.com/";
+ return url;
+ }
+
+ /**
+ * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
+ */
+ private String getWalledGardenPattern() {
+ String pattern = Settings.Secure.getString(mContentResolver,
+ Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
+ if (TextUtils.isEmpty(pattern))
+ return "<title>.*Google.*</title>";
+ return pattern;
}
/**
* @see android.provider.Settings.Secure#WIFI_WATCHDOG_ON
*/
private boolean isWatchdogEnabled() {
- return Settings.Secure.getInt(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_AP_COUNT
- */
- private int getApCount() {
return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_AP_COUNT, 2);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT
- */
- private int getInitialIgnoredPingCount() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT , 2);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_COUNT
- */
- private int getPingCount() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_PING_COUNT, 4);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_TIMEOUT_MS
- */
- private int getPingTimeoutMs() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS, 500);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_PING_DELAY_MS
- */
- private int getPingDelayMs() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS, 250);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED
- */
- private Boolean isWalledGardenTestEnabled() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, 1) == 1;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_URL
- */
- private String getWalledGardenUrl() {
- String url = Settings.Secure.getString(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL);
- if (TextUtils.isEmpty(url)) return "http://www.google.com/";
- return url;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WALLED_GARDEN_PATTERN
- */
- private String getWalledGardenPattern() {
- String pattern = Settings.Secure.getString(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_PATTERN);
- if (TextUtils.isEmpty(pattern)) return "<title>.*Google.*</title>";
- return pattern;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE
- */
- private int getAcceptablePacketLossPercentage() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_ACCEPTABLE_PACKET_LOSS_PERCENTAGE, 25);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS
- */
- private int getMaxApChecks() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_MAX_AP_CHECKS, 7);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED
- */
- private boolean isBackgroundCheckEnabled() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_ENABLED, 1) == 1;
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS
- */
- private int getBackgroundCheckDelayMs() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_DELAY_MS, 60000);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS
- */
- private int getBackgroundCheckTimeoutMs() {
- return Settings.Secure.getInt(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_BACKGROUND_CHECK_TIMEOUT_MS, 1000);
- }
-
- /**
- * @see android.provider.Settings.Secure#WIFI_WATCHDOG_WATCH_LIST
- * @return the comma-separated list of SSIDs
- */
- private String getWatchList() {
- return Settings.Secure.getString(mContentResolver,
- Settings.Secure.WIFI_WATCHDOG_WATCH_LIST);
- }
-
- /**
- * Registers to receive the necessary Wi-Fi broadcasts.
- */
- private void registerForWifiBroadcasts() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
- intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
- mContext.registerReceiver(mReceiver, intentFilter);
- }
-
- /**
- * Unregisters from receiving the Wi-Fi broadcasts.
- */
- private void unregisterForWifiBroadcasts() {
- mContext.unregisterReceiver(mReceiver);
- }
-
- /**
- * Creates the main watchdog thread, including waiting for the handler to be
- * created.
- */
- private void createThread() {
- mThread = new WifiWatchdogThread();
- mThread.start();
- waitForHandlerCreation();
- }
-
- /**
- * Unregister broadcasts and quit the watchdog thread
- */
- //TODO: Change back to running WWS when needed
-// private void quit() {
-// unregisterForWifiBroadcasts();
-// mContext.getContentResolver().unregisterContentObserver(mContentObserver);
-// mHandler.removeAllActions();
-// mHandler.getLooper().quit();
-// }
-
- /**
- * Waits for the main watchdog thread to create the handler.
- */
- private void waitForHandlerCreation() {
- synchronized(this) {
- while (mHandler == null) {
- try {
- // Wait for the handler to be set by the other thread
- wait();
- } catch (InterruptedException e) {
- Slog.e(TAG, "Interrupted while waiting on handler.");
- }
- }
- }
- }
-
- // Utility methods
-
- /**
- * Logs with the current thread.
- */
- private static void myLogV(String message) {
- Slog.v(TAG, "(" + Thread.currentThread().getName() + ") " + message);
- }
-
- private static void myLogD(String message) {
- Slog.d(TAG, "(" + Thread.currentThread().getName() + ") " + message);
- }
-
- /**
- * Gets the first DNS of the current AP.
- *
- * @return The first DNS of the current AP.
- */
- private InetAddress getDns() {
- if (mConnectivityManager == null) {
- mConnectivityManager = (ConnectivityManager)mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- }
-
- LinkProperties linkProperties = mConnectivityManager.getLinkProperties(
- ConnectivityManager.TYPE_WIFI);
- if (linkProperties == null) return null;
-
- Collection<InetAddress> dnses = linkProperties.getDnses();
- if (dnses == null || dnses.size() == 0) return null;
-
- return dnses.iterator().next();
- }
-
- /**
- * Checks whether the DNS can be reached using multiple attempts according
- * to the current setting values.
- *
- * @return Whether the DNS is reachable
- */
- private boolean checkDnsConnectivity() {
- InetAddress dns = getDns();
- if (dns == null) {
- if (V) {
- myLogV("checkDnsConnectivity: Invalid DNS, returning false");
- }
- return false;
- }
-
- if (V) {
- myLogV("checkDnsConnectivity: Checking " + dns.getHostAddress() + " for connectivity");
- }
-
- int numInitialIgnoredPings = getInitialIgnoredPingCount();
- int numPings = getPingCount();
- int pingDelay = getPingDelayMs();
- int acceptableLoss = getAcceptablePacketLossPercentage();
-
- /** See {@link Secure#WIFI_WATCHDOG_INITIAL_IGNORED_PING_COUNT} */
- int ignoredPingCounter = 0;
- int pingCounter = 0;
- int successCounter = 0;
-
- // No connectivity check needed
- if (numPings == 0) {
- return true;
- }
-
- // Do the initial pings that we ignore
- for (; ignoredPingCounter < numInitialIgnoredPings; ignoredPingCounter++) {
- if (shouldCancel()) return false;
-
- boolean dnsAlive = DnsPinger.isDnsReachable(dns, getPingTimeoutMs());
- if (dnsAlive) {
- /*
- * Successful "ignored" pings are *not* ignored (they count in the total number
- * of pings), but failures are really ignored.
- */
-
- // TODO: This is confusing logic and should be rewitten
- // Here, successful 'ignored' pings are interpreted as a success in the below loop
- pingCounter++;
- successCounter++;
- }
-
- if (V) {
- Slog.v(TAG, (dnsAlive ? " +" : " Ignored: -"));
- }
-
- if (shouldCancel()) return false;
-
- try {
- Thread.sleep(pingDelay);
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted while pausing between pings", e);
- }
- }
-
- // Do the pings that we use to measure packet loss
- for (; pingCounter < numPings; pingCounter++) {
- if (shouldCancel()) return false;
-
- if (DnsPinger.isDnsReachable(dns, getPingTimeoutMs())) {
- successCounter++;
- if (V) {
- Slog.v(TAG, " +");
- }
- } else {
- if (V) {
- Slog.v(TAG, " -");
- }
- }
-
- if (shouldCancel()) return false;
-
- try {
- Thread.sleep(pingDelay);
- } catch (InterruptedException e) {
- Slog.w(TAG, "Interrupted while pausing between pings", e);
- }
- }
-
- //TODO: Integer division might cause problems down the road...
- int packetLossPercentage = 100 * (numPings - successCounter) / numPings;
- if (D) {
- Slog.d(TAG, packetLossPercentage
- + "% packet loss (acceptable is " + acceptableLoss + "%)");
- }
-
- return !shouldCancel() && (packetLossPercentage <= acceptableLoss);
- }
-
- private boolean backgroundCheckDnsConnectivity() {
- InetAddress dns = getDns();
-
- if (dns == null) {
- if (V) {
- myLogV("backgroundCheckDnsConnectivity: DNS is empty, returning false");
- }
- return false;
- }
-
- if (V) {
- myLogV("backgroundCheckDnsConnectivity: Background checking " +
- dns.getHostAddress() + " for connectivity");
- }
-
- return DnsPinger.isDnsReachable(dns, getBackgroundCheckTimeoutMs());
- }
-
- /**
- * Signals the current action to cancel.
- */
- private void cancelCurrentAction() {
- mShouldCancel = true;
- }
-
- /**
- * Helper to check whether to cancel.
- *
- * @return Whether to cancel processing the action.
- */
- private boolean shouldCancel() {
- if (V && mShouldCancel) {
- myLogV("shouldCancel: Cancelling");
- }
-
- return mShouldCancel;
- }
-
- // Wi-Fi initiated callbacks (could be executed in another thread)
-
- /**
- * Called when connected to an AP (this can be the next AP in line, or
- * it can be a completely different network).
- *
- * @param ssid The SSID of the access point.
- * @param bssid The BSSID of the access point.
- */
- private void onConnected(String ssid, String bssid) {
- if (V) {
- myLogV("onConnected: SSID: " + ssid + ", BSSID: " + bssid);
- }
-
- /*
- * The current action being processed by the main watchdog thread is now
- * stale, so cancel it.
- */
- cancelCurrentAction();
-
- if ((mSsid == null) || !mSsid.equals(ssid)) {
- /*
- * This is a different network than what the main watchdog thread is
- * processing, dispatch the network change message on the main thread.
- */
- mHandler.dispatchNetworkChanged(ssid);
- }
-
- if (requiresWatchdog(ssid, bssid)) {
- if (D) {
- myLogD(ssid + " (" + bssid + ") requires the watchdog");
- }
-
- // This access point requires a watchdog, so queue the check on the main thread
- mHandler.checkAp(new AccessPoint(ssid, bssid));
-
- } else {
- if (D) {
- myLogD(ssid + " (" + bssid + ") does not require the watchdog");
- }
-
- // This access point does not require a watchdog, so queue idle on the main thread
- mHandler.idle();
- }
- if (isWalledGardenTestEnabled()) mHandler.checkWalledGarden(ssid);
- }
-
- /**
- * Called when Wi-Fi is enabled.
- */
- private void onEnabled() {
- cancelCurrentAction();
- // Queue a hard-reset of the state on the main thread
- mHandler.reset();
- }
-
- /**
- * Called when disconnected (or some other event similar to being disconnected).
- */
- private void onDisconnected() {
- if (V) {
- myLogV("onDisconnected");
- }
-
- /*
- * Disconnected from an access point, the action being processed by the
- * watchdog thread is now stale, so cancel it.
- */
- cancelCurrentAction();
- // Dispatch the disconnected to the main watchdog thread
- mHandler.dispatchDisconnected();
- // Queue the action to go idle
- mHandler.idle();
- }
-
- /**
- * Checks whether an access point requires watchdog monitoring.
- *
- * @param ssid The SSID of the access point.
- * @param bssid The BSSID of the access point.
- * @return Whether the access point/network should be monitored by the
- * watchdog.
- */
- private boolean requiresWatchdog(String ssid, String bssid) {
- if (V) {
- myLogV("requiresWatchdog: SSID: " + ssid + ", BSSID: " + bssid);
- }
-
- WifiInfo info = null;
- if (ssid == null) {
- /*
- * This is called from a Wi-Fi callback, so assume the WifiInfo does
- * not have stale data.
- */
- info = mWifiManager.getConnectionInfo();
- ssid = info.getSSID();
- if (ssid == null) {
- // It's still null, give up
- if (V) {
- Slog.v(TAG, " Invalid SSID, returning false");
- }
- return false;
- }
- }
-
- if (TextUtils.isEmpty(bssid)) {
- // Similar as above
- if (info == null) {
- info = mWifiManager.getConnectionInfo();
- }
- bssid = info.getBSSID();
- if (TextUtils.isEmpty(bssid)) {
- // It's still null, give up
- if (V) {
- Slog.v(TAG, " Invalid BSSID, returning false");
- }
- return false;
- }
- }
-
- if (!isOnWatchList(ssid)) {
- if (V) {
- Slog.v(TAG, " SSID not on watch list, returning false");
- }
- return false;
- }
-
- // The watchdog only monitors networks with multiple APs
- if (!hasRequiredNumberOfAps(ssid)) {
- return false;
- }
-
- return true;
- }
-
- private boolean isOnWatchList(String ssid) {
- String watchList;
-
- if (ssid == null || (watchList = getWatchList()) == null) {
- return false;
- }
-
- String[] list = watchList.split(" *, *");
-
- for (String name : list) {
- if (ssid.equals(name)) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Checks if the current scan results have multiple access points with an SSID.
- *
- * @param ssid The SSID to check.
- * @return Whether the SSID has multiple access points.
- */
- private boolean hasRequiredNumberOfAps(String ssid) {
- List<ScanResult> results = mWifiManager.getScanResults();
- if (results == null) {
- if (V) {
- myLogV("hasRequiredNumberOfAps: Got null scan results, returning false");
- }
- return false;
- }
-
- int numApsRequired = getApCount();
- int numApsFound = 0;
- int resultsSize = results.size();
- for (int i = 0; i < resultsSize; i++) {
- ScanResult result = results.get(i);
- if (result == null) continue;
- if (result.SSID == null) continue;
-
- if (result.SSID.equals(ssid)) {
- numApsFound++;
-
- if (numApsFound >= numApsRequired) {
- if (V) {
- myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning true");
- }
- return true;
- }
- }
- }
-
- if (V) {
- myLogV("hasRequiredNumberOfAps: SSID: " + ssid + ", returning false");
- }
- return false;
- }
-
- // Watchdog logic (assume all of these methods will be in our main thread)
-
- /**
- * Handles a Wi-Fi network change (for example, from networkA to networkB).
- */
- private void handleNetworkChanged(String ssid) {
- // Set the SSID being monitored to the new SSID
- mSsid = ssid;
- // Set various state to that when being idle
- setIdleState(true);
- }
-
- /**
- * Handles checking whether an AP is a "good" AP. If not, it will be blacklisted.
- *
- * @param ap The access point to check.
- */
- private void handleCheckAp(AccessPoint ap) {
- // Reset the cancel state since this is the entry point of this action
- mShouldCancel = false;
-
- if (V) {
- myLogV("handleCheckAp: AccessPoint: " + ap);
- }
-
- // Make sure we are not sleeping
- if (mState == WatchdogState.SLEEP) {
- if (V) {
- Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
- }
- return;
- }
-
- mState = WatchdogState.CHECKING_AP;
-
- /*
- * Checks to make sure we haven't exceeded the max number of checks
- * we're allowed per network
- */
- incrementBssidCheckCount();
- if (getBssidCheckCount() > getMaxApChecks()) {
- if (V) {
- Slog.v(TAG, " Passed the max attempts (" + getMaxApChecks()
- + "), going to sleep for " + mSsid);
- }
- mHandler.sleep(mSsid);
- return;
- }
-
- // Do the check
- boolean isApAlive = checkDnsConnectivity();
-
- if (V) {
- Slog.v(TAG, " Is it alive: " + isApAlive);
- }
-
- // Take action based on results
- if (isApAlive) {
- handleApAlive(ap);
- } else {
- handleApUnresponsive(ap);
- }
- }
-
- /**
- * Handles the case when an access point is alive.
- *
- * @param ap The access point.
- */
- private void handleApAlive(AccessPoint ap) {
- // Check whether we are stale and should cancel
- if (shouldCancel()) return;
- // We're satisfied with this AP, so go idle
- setIdleState(false);
-
- if (D) {
- myLogD("AP is alive: " + ap.toString());
- }
-
- // Queue the next action to be a background check
- mHandler.backgroundCheckAp(ap);
- }
-
- /**
- * Handles an unresponsive AP by blacklisting it.
- *
- * @param ap The access point.
- */
- private void handleApUnresponsive(AccessPoint ap) {
- // Check whether we are stale and should cancel
- if (shouldCancel()) return;
- // This AP is "bad", switch to another
- mState = WatchdogState.SWITCHING_AP;
-
- if (D) {
- myLogD("AP is dead: " + ap.toString());
- }
-
- // Black list this "bad" AP, this will cause an attempt to connect to another
- blacklistAp(ap.bssid);
- // Initiate an association to an alternate AP
- mWifiManager.reassociate();
- }
-
- private void blacklistAp(String bssid) {
- if (TextUtils.isEmpty(bssid)) {
- return;
- }
-
- // Before taking action, make sure we should not cancel our processing
- if (shouldCancel()) return;
-
- mWifiManager.addToBlacklist(bssid);
-
- if (D) {
- myLogD("Blacklisting " + bssid);
- }
- }
-
- /**
- * Handles a single background check. If it fails, it should trigger a
- * normal check. If it succeeds, it should queue another background check.
- *
- * @param ap The access point to do a background check for. If this is no
- * longer the current AP, it is okay to return without any
- * processing.
- */
- private void handleBackgroundCheckAp(AccessPoint ap) {
- // Reset the cancel state since this is the entry point of this action
- mShouldCancel = false;
-
- if (V) {
- myLogV("handleBackgroundCheckAp: AccessPoint: " + ap);
- }
-
- // Make sure we are not sleeping
- if (mState == WatchdogState.SLEEP) {
- if (V) {
- Slog.v(TAG, " handleBackgroundCheckAp: Sleeping (in " + mSsid + "), so returning");
- }
- return;
- }
-
- // Make sure the AP we're supposed to be background checking is still the active one
- WifiInfo info = mWifiManager.getConnectionInfo();
- if (info.getSSID() == null || !info.getSSID().equals(ap.ssid)) {
- if (V) {
- myLogV("handleBackgroundCheckAp: We are no longer connected to "
- + ap + ", and instead are on " + info);
- }
- return;
- }
-
- if (info.getBSSID() == null || !info.getBSSID().equals(ap.bssid)) {
- if (V) {
- myLogV("handleBackgroundCheckAp: We are no longer connected to "
- + ap + ", and instead are on " + info);
- }
- return;
- }
-
- // Do the check
- boolean isApAlive = backgroundCheckDnsConnectivity();
-
- if (V && !isApAlive) {
- Slog.v(TAG, " handleBackgroundCheckAp: Is it alive: " + isApAlive);
- }
-
- if (shouldCancel()) {
- return;
- }
-
- // Take action based on results
- if (isApAlive) {
- // Queue another background check
- mHandler.backgroundCheckAp(ap);
-
- } else {
- if (D) {
- myLogD("Background check failed for " + ap.toString());
- }
-
- // Queue a normal check, so it can take proper action
- mHandler.checkAp(ap);
- }
- }
-
- /**
- * Handles going to sleep for this network. Going to sleep means we will not
- * monitor this network anymore.
- *
- * @param ssid The network that will not be monitored anymore.
- */
- private void handleSleep(String ssid) {
- // Make sure the network we're trying to sleep in is still the current network
- if (ssid != null && ssid.equals(mSsid)) {
- mState = WatchdogState.SLEEP;
-
- if (D) {
- myLogD("Going to sleep for " + ssid);
- }
-
- /*
- * Before deciding to go to sleep, we may have checked a few APs
- * (and blacklisted them). Clear the blacklist so the AP with best
- * signal is chosen.
- */
- mWifiManager.clearBlacklist();
-
- if (V) {
- myLogV("handleSleep: Set state to SLEEP and cleared blacklist");
- }
- }
- }
-
- /**
- * Handles an access point disconnection.
- */
- private void handleDisconnected() {
- /*
- * We purposefully do not change mSsid to null. This is to handle
- * disconnected followed by connected better (even if there is some
- * duration in between). For example, if the watchdog went to sleep in a
- * network, and then the phone goes to sleep, when the phone wakes up we
- * still want to be in the sleeping state. When the phone went to sleep,
- * we would have gotten a disconnected event which would then set mSsid
- * = null. This is bad, since the following connect would cause us to do
- * the "network is good?" check all over again. */
-
- /*
- * Set the state as if we were idle (don't come out of sleep, only
- * hard reset and network changed should do that.
- */
- setIdleState(false);
- }
-
- /**
- * Handles going idle. Idle means we are satisfied with the current state of
- * things, but if a new connection occurs we'll re-evaluate.
- */
- private void handleIdle() {
- // Reset the cancel state since this is the entry point for this action
- mShouldCancel = false;
-
- if (V) {
- myLogV("handleSwitchToIdle");
- }
-
- // If we're sleeping, don't do anything
- if (mState == WatchdogState.SLEEP) {
- Slog.v(TAG, " Sleeping (in " + mSsid + "), so returning");
- return;
- }
-
- // Set the idle state
- setIdleState(false);
-
- if (V) {
- Slog.v(TAG, " Set state to IDLE");
- }
- }
-
- /**
- * Sets the state as if we are going idle.
- */
- private void setIdleState(boolean forceIdleState) {
- // Setting idle state does not kick us out of sleep unless the forceIdleState is set
- if (forceIdleState || (mState != WatchdogState.SLEEP)) {
- mState = WatchdogState.IDLE;
- }
- resetBssidCheckCount();
- }
-
- /**
- * Handles a hard reset. A hard reset is rarely used, but when used it
- * should revert anything done by the watchdog monitoring.
- */
- private void handleReset() {
- mWifiManager.clearBlacklist();
- setIdleState(true);
- }
-
- // Inner classes
-
- /**
- * Possible states for the watchdog to be in.
- */
- private static enum WatchdogState {
- /** The watchdog is currently idle, but it is still responsive to future AP checks in this network. */
- IDLE,
- /** The watchdog is sleeping, so it will not try any AP checks for the network. */
- SLEEP,
- /** The watchdog is currently checking an AP for connectivity. */
- CHECKING_AP,
- /** The watchdog is switching to another AP in the network. */
- SWITCHING_AP
- }
-
- private int getBssidCheckCount() {
- return mBssidCheckCount;
- }
-
- private void incrementBssidCheckCount() {
- mBssidCheckCount++;
- }
-
- private void resetBssidCheckCount() {
- this.mBssidCheckCount = 0;
- }
-
- /**
- * The main thread for the watchdog monitoring. This will be turned into a
- * {@link Looper} thread.
- */
- private class WifiWatchdogThread extends Thread {
- WifiWatchdogThread() {
- super("WifiWatchdogThread");
- }
-
- @Override
- public void run() {
- // Set this thread up so the handler will work on it
- Looper.prepare();
-
- synchronized(WifiWatchdogService.this) {
- mHandler = new WifiWatchdogHandler();
-
- // Notify that the handler has been created
- WifiWatchdogService.this.notify();
- }
-
- // Listen for messages to the handler
- Looper.loop();
- }
- }
-
- /**
- * The main thread's handler. There are 'actions', and just general
- * 'messages'. There should only ever be one 'action' in the queue (aside
- * from the one being processed, if any). There may be multiple messages in
- * the queue. So, actions are replaced by more recent actions, where as
- * messages will be executed for sure. Messages end up being used to just
- * change some state, and not really take any action.
- * <p>
- * There is little logic inside this class, instead methods of the form
- * "handle___" are called in the main {@link WifiWatchdogService}.
- */
- private class WifiWatchdogHandler extends Handler {
- /** Check whether the AP is "good". The object will be an {@link AccessPoint}. */
- static final int ACTION_CHECK_AP = 1;
- /** Go into the idle state. */
- static final int ACTION_IDLE = 2;
- /**
- * Performs a periodic background check whether the AP is still "good".
- * The object will be an {@link AccessPoint}.
- */
- static final int ACTION_BACKGROUND_CHECK_AP = 3;
- /** Check whether the connection is a walled garden */
- static final int ACTION_CHECK_WALLED_GARDEN = 4;
-
- /**
- * Go to sleep for the current network. We are conservative with making
- * this a message rather than action. We want to make sure our main
- * thread sees this message, but if it were an action it could be
- * removed from the queue and replaced by another action. The main
- * thread will ensure when it sees the message that the state is still
- * valid for going to sleep.
- * <p>
- * For an explanation of sleep, see {@link android.provider.Settings.Secure#WIFI_WATCHDOG_MAX_AP_CHECKS}.
- */
- static final int MESSAGE_SLEEP = 101;
- /** Disables the watchdog. */
- static final int MESSAGE_DISABLE_WATCHDOG = 102;
- /** The network has changed. */
- static final int MESSAGE_NETWORK_CHANGED = 103;
- /** The current access point has disconnected. */
- static final int MESSAGE_DISCONNECTED = 104;
- /** Performs a hard-reset on the watchdog state. */
- static final int MESSAGE_RESET = 105;
-
- /* Walled garden detection */
- private String mLastSsid;
- private long mLastTime;
- private final long MIN_WALLED_GARDEN_TEST_INTERVAL = 15 * 60 * 1000; //15 minutes
-
- void checkWalledGarden(String ssid) {
- sendMessage(obtainMessage(ACTION_CHECK_WALLED_GARDEN, ssid));
- }
-
- void checkAp(AccessPoint ap) {
- removeAllActions();
- sendMessage(obtainMessage(ACTION_CHECK_AP, ap));
- }
-
- void backgroundCheckAp(AccessPoint ap) {
- if (!isBackgroundCheckEnabled()) return;
-
- removeAllActions();
- sendMessageDelayed(obtainMessage(ACTION_BACKGROUND_CHECK_AP, ap),
- getBackgroundCheckDelayMs());
- }
-
- void idle() {
- removeAllActions();
- sendMessage(obtainMessage(ACTION_IDLE));
- }
-
- void sleep(String ssid) {
- removeAllActions();
- sendMessage(obtainMessage(MESSAGE_SLEEP, ssid));
- }
-
- void disableWatchdog() {
- removeAllActions();
- sendMessage(obtainMessage(MESSAGE_DISABLE_WATCHDOG));
- }
-
- void dispatchNetworkChanged(String ssid) {
- removeAllActions();
- sendMessage(obtainMessage(MESSAGE_NETWORK_CHANGED, ssid));
- }
-
- void dispatchDisconnected() {
- removeAllActions();
- sendMessage(obtainMessage(MESSAGE_DISCONNECTED));
- }
-
- void reset() {
- removeAllActions();
- sendMessage(obtainMessage(MESSAGE_RESET));
- }
-
- private void removeAllActions() {
- removeMessages(ACTION_CHECK_AP);
- removeMessages(ACTION_IDLE);
- removeMessages(ACTION_BACKGROUND_CHECK_AP);
- }
-
- @Override
- public void handleMessage(Message msg) {
- if (V) {
- myLogV("handleMessage: " + msg.what);
- }
- switch (msg.what) {
- case MESSAGE_NETWORK_CHANGED:
- handleNetworkChanged((String) msg.obj);
- break;
- case ACTION_CHECK_AP:
- handleCheckAp((AccessPoint) msg.obj);
- break;
- case ACTION_BACKGROUND_CHECK_AP:
- handleBackgroundCheckAp((AccessPoint) msg.obj);
- break;
- case ACTION_CHECK_WALLED_GARDEN:
- handleWalledGardenCheck((String) msg.obj);
- break;
- case MESSAGE_SLEEP:
- handleSleep((String) msg.obj);
- break;
- case ACTION_IDLE:
- handleIdle();
- break;
- case MESSAGE_DISABLE_WATCHDOG:
- handleIdle();
- break;
- case MESSAGE_DISCONNECTED:
- handleDisconnected();
- break;
- case MESSAGE_RESET:
- handleReset();
- break;
- }
- }
-
- /**
- * DNS based detection techniques do not work at all hotspots. The one sure way to check
- * a walled garden is to see if a URL fetch on a known address fetches the data we
- * expect
- */
- private boolean isWalledGardenConnection() {
- InputStream in = null;
- HttpURLConnection urlConnection = null;
- try {
- URL url = new URL(getWalledGardenUrl());
- urlConnection = (HttpURLConnection) url.openConnection();
- in = new BufferedInputStream(urlConnection.getInputStream());
- Scanner scanner = new Scanner(in);
- if (scanner.findInLine(getWalledGardenPattern()) != null) {
- return false;
- } else {
- return true;
- }
- } catch (IOException e) {
- return false;
- } finally {
- if (in != null) {
- try {
- in.close();
- } catch (IOException e) {
- }
- }
- if (urlConnection != null) urlConnection.disconnect();
- }
- }
-
- private void handleWalledGardenCheck(String ssid) {
- long currentTime = System.currentTimeMillis();
- //Avoid a walled garden test on the same network if one was already done
- //within MIN_WALLED_GARDEN_TEST_INTERVAL. This will handle scenarios where
- //there are frequent network disconnections
- if (ssid.equals(mLastSsid) &&
- (currentTime - mLastTime) < MIN_WALLED_GARDEN_TEST_INTERVAL) {
- return;
- }
-
- mLastTime = currentTime;
- mLastSsid = ssid;
-
- if (isWalledGardenConnection()) {
- Uri uri = Uri.parse("http://www.google.com");
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
- Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
- }
- }
- }
-
- /**
- * Receives Wi-Fi broadcasts.
- * <p>
- * There is little logic in this class, instead methods of the form "on___"
- * are called in the {@link WifiWatchdogService}.
- */
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
- handleNetworkStateChanged(
- (NetworkInfo) intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO));
- } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
- handleWifiStateChanged(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
- WifiManager.WIFI_STATE_UNKNOWN));
- }
- }
-
- private void handleNetworkStateChanged(NetworkInfo info) {
- if (V) {
- myLogV("Receiver.handleNetworkStateChanged: NetworkInfo: "
- + info);
- }
-
- switch (info.getState()) {
- case CONNECTED:
- WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
- if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
- if (V) {
- myLogV("handleNetworkStateChanged: Got connected event but SSID or BSSID are null. SSID: "
- + wifiInfo.getSSID()
- + ", BSSID: "
- + wifiInfo.getBSSID() + ", ignoring event");
- }
- return;
- }
- onConnected(wifiInfo.getSSID(), wifiInfo.getBSSID());
- break;
-
- case DISCONNECTED:
- onDisconnected();
- break;
- }
- }
-
- private void handleWifiStateChanged(int wifiState) {
- if (wifiState == WifiManager.WIFI_STATE_DISABLED) {
- onDisconnected();
- } else if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
- onEnabled();
- }
- }
- };
-
- /**
- * Describes an access point by its SSID and BSSID.
- *
- */
- private static class AccessPoint {
- String ssid;
- String bssid;
-
- /**
- * @param ssid cannot be null
- * @param bssid cannot be null
- */
- AccessPoint(String ssid, String bssid) {
- if (ssid == null || bssid == null) {
- Slog.e(TAG, String.format("(%s) INVALID ACCESSPOINT: (%s, %s)",
- Thread.currentThread().getName(),ssid,bssid));
- }
- this.ssid = ssid;
- this.bssid = bssid;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof AccessPoint)) return false;
- AccessPoint otherAp = (AccessPoint) o;
-
- // Either we both have a null, or our SSIDs and BSSIDs are equal
- return ssid.equals(otherAp.ssid) && bssid.equals(otherAp.bssid);
- }
-
- @Override
- public int hashCode() {
- return ssid.hashCode() + bssid.hashCode();
- }
-
- @Override
- public String toString() {
- return ssid + " (" + bssid + ")";
- }
- }
-
- /**
- * Performs a simple DNS "ping" by sending a "server status" query packet to
- * the DNS server. As long as the server replies, we consider it a success.
- * <p>
- * We do not use a simple hostname lookup because that could be cached and
- * the API may not differentiate between a time out and a failure lookup
- * (which we really care about).
- */
- private static class DnsPinger {
-
- /** Number of bytes for the query */
- private static final int DNS_QUERY_BASE_SIZE = 33;
-
- /** The DNS port */
- private static final int DNS_PORT = 53;
-
- /** Used to generate IDs */
- private static Random sRandom = new Random();
-
- static boolean isDnsReachable(InetAddress dnsAddress, int timeout) {
- DatagramSocket socket = null;
- try {
- socket = new DatagramSocket();
-
- // Set some socket properties
- socket.setSoTimeout(timeout);
-
- byte[] buf = new byte[DNS_QUERY_BASE_SIZE];
- fillQuery(buf);
-
- // Send the DNS query
-
- DatagramPacket packet = new DatagramPacket(buf,
- buf.length, dnsAddress, DNS_PORT);
- socket.send(packet);
-
- // Wait for reply (blocks for the above timeout)
- DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
- socket.receive(replyPacket);
-
- // If a timeout occurred, an exception would have been thrown. We got a reply!
- return true;
-
- } catch (SocketException e) {
- if (V) {
- Slog.v(TAG, "DnsPinger.isReachable received SocketException", e);
- }
- return false;
-
- } catch (UnknownHostException e) {
- if (V) {
- Slog.v(TAG, "DnsPinger.isReachable is unable to resolve the DNS host", e);
- }
- return false;
-
- } catch (SocketTimeoutException e) {
- return false;
-
- } catch (IOException e) {
- if (V) {
- Slog.v(TAG, "DnsPinger.isReachable got an IOException", e);
- }
- return false;
-
- } catch (Exception e) {
- if (V) {
- Slog.d(TAG, "DnsPinger.isReachable got an unknown exception", e);
- }
- return false;
- } finally {
- if (socket != null) {
- socket.close();
- }
- }
- }
-
- private static void fillQuery(byte[] buf) {
-
- /*
- * See RFC2929 (though the bit tables in there are misleading for
- * us. For example, the recursion desired bit is the 0th bit for us,
- * but looking there it would appear as the 7th bit of the byte
- */
-
- // Make sure it's all zeroed out
- for (int i = 0; i < buf.length; i++) buf[i] = 0;
-
- // Form a query for www.android.com
-
- // [0-1] bytes are an ID, generate random ID for this query
- buf[0] = (byte) sRandom.nextInt(256);
- buf[1] = (byte) sRandom.nextInt(256);
-
- // [2-3] bytes are for flags.
- buf[2] = 1; // Recursion desired
-
- // [4-5] bytes are for the query count
- buf[5] = 1; // One query
-
- // [6-7] [8-9] [10-11] are all counts of other fields we don't use
-
- // [12-15] for www
- writeString(buf, 12, "www");
-
- // [16-23] for android
- writeString(buf, 16, "android");
-
- // [24-27] for com
- writeString(buf, 24, "com");
-
- // [29-30] bytes are for QTYPE, set to 1
- buf[30] = 1;
-
- // [31-32] bytes are for QCLASS, set to 1
- buf[32] = 1;
- }
-
- private static void writeString(byte[] buf, int startPos, String string) {
- int pos = startPos;
-
- // Write the length first
- buf[pos++] = (byte) string.length();
- for (int i = 0; i < string.length(); i++) {
- buf[pos++] = (byte) string.charAt(i);
- }
- }
+ Settings.Secure.WIFI_WATCHDOG_ON, 1) == 1;
}
}
diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java
index a80bc04..524dd40 100644
--- a/services/java/com/android/server/net/NetworkStatsService.java
+++ b/services/java/com/android/server/net/NetworkStatsService.java
@@ -71,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;
@@ -214,7 +215,9 @@
// TODO: consider shipping with this enabled by default
mNetworkManager.setBandwidthControlEnabled(true);
} catch (RemoteException e) {
- Slog.e(TAG, "problem enabling bandwidth controls", 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");
@@ -1055,6 +1058,10 @@
}
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() {
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index ffabb7b..dea67f3 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -94,6 +94,9 @@
*/
public PhoneNumberFormattingTextWatcher(String countryCode) {
if (countryCode == null) throw new IllegalArgumentException();
+ // TODO: remove this once CountryDetector.detectCountry().getCountryIso() is fixed to always
+ // return uppercase. Tracked at b/4941319.
+ countryCode = countryCode.toUpperCase(Locale.ENGLISH);
mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
}
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/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/layout/view_group_margin_mixed.xml b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml
new file mode 100644
index 0000000..5845b38
--- /dev/null
+++ b/tests/BiDiTests/res/layout/view_group_margin_mixed.xml
@@ -0,0 +1,241 @@
+<?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/view_group_margin_mixed"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="#FFFFFFFF">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="ltr">
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF0000FF">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="0dip"
+ android:layout_marginRight="0dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF000000">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="10dip"
+ android:layout_marginStart="5dip"
+ android:layout_marginRight="20dip"
+ android:layout_marginEnd="5dip"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="rtl">
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF0000FF">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="0dip"
+ android:layout_marginRight="0dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF000000">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="10dip"
+ android:layout_marginStart="5dip"
+ android:layout_marginRight="20dip"
+ android:layout_marginEnd="5dip"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="inherit">
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF0000FF">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="0dip"
+ android:layout_marginRight="0dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF000000">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="10dip"
+ android:layout_marginStart="5dip"
+ android:layout_marginRight="20dip"
+ android:layout_marginEnd="5dip"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <LinearLayout android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layoutDirection="locale">
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF0000FF">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="0dip"
+ android:layout_marginRight="0dip"
+ android:layout_marginTop="0dip"
+ android:layout_marginBottom="0dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ <LinearLayout android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="#FF000000">
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:layout_marginLeft="10dip"
+ android:layout_marginStart="5dip"
+ android:layout_marginRight="20dip"
+ android:layout_marginEnd="5dip"
+ android:layout_marginTop="5dip"
+ android:layout_marginBottom="5dip"
+ android:background="#FFFF0000">
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="80dp"
+ android:layout_height="80dp"
+ android:background="#FF00FF00">
+ </FrameLayout>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
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..b1e494a 100644
--- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java
@@ -100,23 +100,24 @@
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);
-
+
addItem(result, "Frame LTR", BiDiTestFrameLayoutLtr.class, R.id.frame_layout_ltr);
addItem(result, "Frame RTL", BiDiTestFrameLayoutRtl.class, R.id.frame_layout_rtl);
addItem(result, "Frame LOC", BiDiTestFrameLayoutLocale.class, R.id.frame_layout_locale);
-
+
addItem(result, "Relative LTR", BiDiTestRelativeLayoutLtr.class, R.id.relative_layout_ltr);
addItem(result, "Relative RTL", BiDiTestRelativeLayoutRtl.class, R.id.relative_layout_rtl);
-
+
addItem(result, "Relative2 LTR", BiDiTestRelativeLayout2Ltr.class, R.id.relative_layout_2_ltr);
addItem(result, "Relative2 RTL", BiDiTestRelativeLayout2Rtl.class, R.id.relative_layout_2_rtl);
addItem(result, "Relative2 LOC", BiDiTestRelativeLayout2Locale.class, R.id.relative_layout_2_locale);
-
+
addItem(result, "Table LTR", BiDiTestTableLayoutLtr.class, R.id.table_layout_ltr);
addItem(result, "Table RTL", BiDiTestTableLayoutRtl.class, R.id.table_layout_rtl);
addItem(result, "Table LOC", BiDiTestTableLayoutLocale.class, R.id.table_layout_locale);
@@ -124,6 +125,15 @@
addItem(result, "ViewPadding", BiDiTestViewPadding.class, R.id.view_padding);
addItem(result, "ViewPadding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
+ addItem(result, "Padding", BiDiTestViewPadding.class, R.id.view_padding);
+ addItem(result, "Padding MIXED", BiDiTestViewPaddingMixed.class, R.id.view_padding_mixed);
+
+ addItem(result, "Margin MIXED", BiDiTestViewGroupMarginMixed.class, R.id.view_group_margin_mixed);
+
+ 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);
+
return result;
}
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/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java
new file mode 100644
index 0000000..ce8c380
--- /dev/null
+++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestViewGroupMarginMixed.java
@@ -0,0 +1,17 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+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 BiDiTestViewGroupMarginMixed extends Fragment {
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.view_group_margin_mixed, container, false);
+ }
+}
diff --git a/tests/CoreTests/MODULE_LICENSE_APACHE2 b/tests/CoreTests/MODULE_LICENSE_APACHE2
deleted file mode 100644
index e69de29..0000000
--- a/tests/CoreTests/MODULE_LICENSE_APACHE2
+++ /dev/null
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index c650021..7aa0617 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -31,6 +31,15 @@
android:hardwareAccelerated="true">
<activity
+ android:name="OpaqueActivity"
+ android:label="_Opaque">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="GetBitmapActivity"
android:label="_GetBitmap">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
index 723f3e8..e1ca756 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/GLTextureViewActivity.java
@@ -19,13 +19,17 @@
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.SurfaceTexture;
-import android.opengl.GLES20;
+import android.opengl.GLUtils;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.TextureView;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
import javax.microedition.khronos.egl.EGL10;
@@ -36,6 +40,12 @@
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import static android.opengl.GLES20.*;
+
@SuppressWarnings({"UnusedDeclaration"})
public class GLTextureViewActivity extends Activity implements TextureView.SurfaceTextureListener {
private RenderThread mRenderThread;
@@ -48,12 +58,14 @@
mTextureView = new TextureView(this);
mTextureView.setSurfaceTextureListener(this);
- setContentView(mTextureView, new FrameLayout.LayoutParams(500, 400, Gravity.CENTER));
+ setContentView(mTextureView, new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
+ Gravity.CENTER));
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- mRenderThread = new RenderThread(surface);
+ mRenderThread = new RenderThread(getResources(), surface);
mRenderThread.start();
mTextureView.setCameraDistance(5000);
@@ -93,13 +105,12 @@
private static final String LOG_TAG = "GLTextureView";
static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
- static final int EGL_SURFACE_TYPE = 0x3033;
- static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;
static final int EGL_OPENGL_ES2_BIT = 4;
private volatile boolean mFinished;
- private SurfaceTexture mSurface;
+ private final Resources mResources;
+ private final SurfaceTexture mSurface;
private EGL10 mEgl;
private EGLDisplay mEglDisplay;
@@ -108,26 +119,96 @@
private EGLSurface mEglSurface;
private GL mGL;
- RenderThread(SurfaceTexture surface) {
+ RenderThread(Resources resources, SurfaceTexture surface) {
+ mResources = resources;
mSurface = surface;
}
+ private static final String sSimpleVS =
+ "attribute vec4 position;\n" +
+ "attribute vec2 texCoords;\n" +
+ "varying vec2 outTexCoords;\n" +
+ "\nvoid main(void) {\n" +
+ " outTexCoords = texCoords;\n" +
+ " gl_Position = position;\n" +
+ "}\n\n";
+ private static final String sSimpleFS =
+ "precision mediump float;\n\n" +
+ "varying vec2 outTexCoords;\n" +
+ "uniform sampler2D texture;\n" +
+ "\nvoid main(void) {\n" +
+ " gl_FragColor = texture2D(texture, outTexCoords);\n" +
+ "}\n\n";
+
+ private static final int FLOAT_SIZE_BYTES = 4;
+ private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+ private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+ private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+ private final float[] mTriangleVerticesData = {
+ // X, Y, Z, U, V
+ -1.0f, -1.0f, 0, 0.f, 0.f,
+ 1.0f, -1.0f, 0, 1.f, 0.f,
+ -1.0f, 1.0f, 0, 0.f, 1.f,
+ 1.0f, 1.0f, 0, 1.f, 1.f,
+ };
+
@Override
public void run() {
initGL();
+
+ FloatBuffer triangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length
+ * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ triangleVertices.put(mTriangleVerticesData).position(0);
- float red = 1.0f;
+ int texture = loadTexture(R.drawable.large_photo);
+ int program = buildProgram(sSimpleVS, sSimpleFS);
+
+ int attribPosition = glGetAttribLocation(program, "position");
+ checkGlError();
+
+ int attribTexCoords = glGetAttribLocation(program, "texCoords");
+ checkGlError();
+
+ int uniformTexture = glGetUniformLocation(program, "texture");
+ checkGlError();
+
+ glBindTexture(GL_TEXTURE_2D, texture);
+ checkGlError();
+
+ glUseProgram(program);
+ checkGlError();
+
+ glEnableVertexAttribArray(attribPosition);
+ checkGlError();
+
+ glEnableVertexAttribArray(attribTexCoords);
+ checkGlError();
+
+ glUniform1i(texture, 0);
+ checkGlError();
+
while (!mFinished) {
checkCurrent();
Log.d(LOG_TAG, "Rendering frame");
- GLES20.glClearColor(red, 0.0f, 0.0f, 1.0f);
+ glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
checkGlError();
- GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+ glClear(GL_COLOR_BUFFER_BIT);
checkGlError();
+ // drawQuad
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+ glVertexAttribPointer(attribPosition, 3, GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+
+ triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+ glVertexAttribPointer(attribTexCoords, 3, GL_FLOAT, false,
+ TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
throw new RuntimeException("Cannot swap buffers");
}
@@ -138,14 +219,90 @@
} catch (InterruptedException e) {
// Ignore
}
-
- red += 0.021f;
- if (red > 1.0f) red = 0.0f;
}
finishGL();
}
+ private int loadTexture(int resource) {
+ int[] textures = new int[1];
+
+ glActiveTexture(GL_TEXTURE0);
+ glGenTextures(1, textures, 0);
+ checkGlError();
+
+ int texture = textures[0];
+ glBindTexture(GL_TEXTURE_2D, texture);
+ checkGlError();
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ Bitmap bitmap = BitmapFactory.decodeResource(mResources, resource);
+
+ GLUtils.texImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bitmap, GL_UNSIGNED_BYTE, 0);
+ checkGlError();
+
+ bitmap.recycle();
+
+ return texture;
+ }
+
+ private int buildProgram(String vertex, String fragment) {
+ int vertexShader = buildShader(vertex, GL_VERTEX_SHADER);
+ if (vertexShader == 0) return 0;
+
+ int fragmentShader = buildShader(fragment, GL_FRAGMENT_SHADER);
+ if (fragmentShader == 0) return 0;
+
+ int program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ checkGlError();
+
+ glAttachShader(program, fragmentShader);
+ checkGlError();
+
+ glLinkProgram(program);
+ checkGlError();
+
+ int[] status = new int[1];
+ glGetProgramiv(program, GL_LINK_STATUS, status, 0);
+ if (status[0] != GL_TRUE) {
+ String error = glGetProgramInfoLog(program);
+ Log.d(LOG_TAG, "Error while linking program:\n" + error);
+ glDeleteShader(vertexShader);
+ glDeleteShader(fragmentShader);
+ glDeleteProgram(program);
+ return 0;
+ }
+
+ return program;
+ }
+
+ private int buildShader(String source, int type) {
+ int shader = glCreateShader(type);
+
+ glShaderSource(shader, source);
+ checkGlError();
+
+ glCompileShader(shader);
+ checkGlError();
+
+ int[] status = new int[1];
+ glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0);
+ if (status[0] != GL_TRUE) {
+ String error = glGetShaderInfoLog(shader);
+ Log.d(LOG_TAG, "Error while compiling shader:\n" + error);
+ glDeleteShader(shader);
+ return 0;
+ }
+
+ return shader;
+ }
+
private void checkEglError() {
int error = mEgl.eglGetError();
if (error != EGL10.EGL_SUCCESS) {
@@ -154,8 +311,8 @@
}
private void checkGlError() {
- int error = GLES20.glGetError();
- if (error != GLES20.GL_NO_ERROR) {
+ int error = glGetError();
+ if (error != GL_NO_ERROR) {
Log.w(LOG_TAG, "GL error = 0x" + Integer.toHexString(error));
}
}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java
new file mode 100644
index 0000000..af45f29
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/OpaqueActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class OpaqueActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final OpaqueView view = new OpaqueView(this);
+ setContentView(view, new FrameLayout.LayoutParams(100, 100, Gravity.CENTER));
+ }
+
+ public static class OpaqueView extends View {
+ public OpaqueView(Context c) {
+ super(c);
+ setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ invalidate();
+ Log.d("OpaqueView", "Invalidate");
+ }
+ });
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ canvas.drawColor(0xffff0000, PorterDuff.Mode.SRC);
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
index 01ee90a..c857ded 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/TextureViewActivity.java
@@ -17,7 +17,6 @@
package com.android.test.hwui;
import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
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/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/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/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl
index 5920bca..690700c 100644
--- a/voip/java/android/net/sip/ISipSessionListener.aidl
+++ b/voip/java/android/net/sip/ISipSessionListener.aidl
@@ -72,6 +72,14 @@
void onCallBusy(in ISipSession session);
/**
+ * Called when the call is being transferred to a new one.
+ *
+ * @param newSession the new session that the call will be transferred to
+ * @param sessionDescription the new peer's session description
+ */
+ void onCallTransferring(in ISipSession newSession, String sessionDescription);
+
+ /**
* Called when an error occurs during session initialization and
* termination.
*
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index b46f8268..2666b69 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -170,6 +170,7 @@
private SipProfile mLocalProfile;
private SipAudioCall.Listener mListener;
private SipSession mSipSession;
+ private SipSession mTransferringSession;
private long mSessionId = System.currentTimeMillis();
private String mPeerSd;
@@ -347,6 +348,27 @@
}
}
+ private synchronized void transferToNewSession() {
+ if (mTransferringSession == null) return;
+ SipSession origin = mSipSession;
+ mSipSession = mTransferringSession;
+ mTransferringSession = null;
+
+ // stop the replaced call.
+ if (mAudioStream != null) {
+ mAudioStream.join(null);
+ } else {
+ try {
+ mAudioStream = new AudioStream(InetAddress.getByName(
+ getLocalIp()));
+ } catch (Throwable t) {
+ Log.i(TAG, "transferToNewSession(): " + t);
+ }
+ }
+ if (origin != null) origin.endCall();
+ startAudio();
+ }
+
private SipSession.Listener createListener() {
return new SipSession.Listener() {
@Override
@@ -404,6 +426,13 @@
mPeerSd = sessionDescription;
Log.v(TAG, "onCallEstablished()" + mPeerSd);
+ // TODO: how to notify the UI that the remote party is changed
+ if ((mTransferringSession != null)
+ && (session == mTransferringSession)) {
+ transferToNewSession();
+ return;
+ }
+
Listener listener = mListener;
if (listener != null) {
try {
@@ -420,7 +449,17 @@
@Override
public void onCallEnded(SipSession session) {
- Log.d(TAG, "sip call ended: " + session);
+ Log.d(TAG, "sip call ended: " + session + " mSipSession:" + mSipSession);
+ // reset the trasnferring session if it is the one.
+ if (session == mTransferringSession) {
+ mTransferringSession = null;
+ return;
+ }
+ // or ignore the event if the original session is being
+ // transferred to the new one.
+ if ((mTransferringSession != null) ||
+ (session != mSipSession)) return;
+
Listener listener = mListener;
if (listener != null) {
try {
@@ -489,6 +528,22 @@
public void onRegistrationDone(SipSession session, int duration) {
// irrelevant
}
+
+ @Override
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ Log.v(TAG, "onCallTransferring mSipSession:"
+ + mSipSession + " newSession:" + newSession);
+ mTransferringSession = newSession;
+ // session changing request
+ try {
+ String answer = createAnswer(sessionDescription).encode();
+ newSession.answerCall(answer, SESSION_TIMEOUT);
+ } catch (Throwable e) {
+ Log.e(TAG, "onCallTransferring()", e);
+ newSession.endCall();
+ }
+ }
};
}
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
index 5629b3c..5ba1626 100644
--- a/voip/java/android/net/sip/SipSession.java
+++ b/voip/java/android/net/sip/SipSession.java
@@ -160,6 +160,17 @@
}
/**
+ * Called when the call is being transferred to a new one.
+ *
+ * @hide
+ * @param newSession the new session that the call will be transferred to
+ * @param sessionDescription the new peer's session description
+ */
+ public void onCallTransferring(SipSession newSession,
+ String sessionDescription) {
+ }
+
+ /**
* Called when an error occurs during session initialization and
* termination.
*
@@ -489,6 +500,16 @@
}
}
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ if (mListener != null) {
+ mListener.onCallTransferring(
+ new SipSession(session, SipSession.this.mListener),
+ sessionDescription);
+
+ }
+ }
+
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
if (mListener != null) {
diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java
index 86aca37..f538983 100644
--- a/voip/java/android/net/sip/SipSessionAdapter.java
+++ b/voip/java/android/net/sip/SipSessionAdapter.java
@@ -42,6 +42,10 @@
public void onCallBusy(ISipSession session) {
}
+ public void onCallTransferring(ISipSession session,
+ String sessionDescription) {
+ }
+
public void onCallChangeFailed(ISipSession session, int errorCode,
String message) {
}
diff --git a/voip/java/com/android/server/sip/SipHelper.java b/voip/java/com/android/server/sip/SipHelper.java
index 018e6de..47950e3 100644
--- a/voip/java/com/android/server/sip/SipHelper.java
+++ b/voip/java/com/android/server/sip/SipHelper.java
@@ -314,7 +314,7 @@
}
}
- private ServerTransaction getServerTransaction(RequestEvent event)
+ public ServerTransaction getServerTransaction(RequestEvent event)
throws SipException {
ServerTransaction transaction = event.getServerTransaction();
if (transaction == null) {
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index f8e5b3a..3b0f5460 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -462,17 +462,30 @@
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);
+ 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) {
try {
cleanUpPendingSessions();
@@ -746,18 +759,30 @@
private class IntervalMeasurementProcess implements
SipSessionGroup.KeepAliveProcessCallback {
private static final String TAG = "SipKeepAliveInterval";
- private static final int MAX_INTERVAL = 120; // seconds
- private static final int MIN_INTERVAL = SHORT_EXPIRY_TIME;
+ 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; // in seconds
- private int mMaxInterval = MAX_INTERVAL;
- private int mInterval = MAX_INTERVAL / 2;
- private int mPassCounter = 0;
+ private int mMaxInterval;
+ private int mInterval;
+ private int mPassCount = 0;
+ private int mErrorCount = 0;
- public IntervalMeasurementProcess(SipProfile localProfile) {
+ 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(localProfile, null, null);
// TODO: remove this line once SipWakeupTimer can better handle
@@ -801,8 +826,10 @@
@Override
public void onResponse(boolean portChanged) {
synchronized (SipService.this) {
+ mErrorCount = 0;
+
if (!portChanged) {
- if (++mPassCounter != PASS_THRESHOLD) return;
+ if (++mPassCount != PASS_THRESHOLD) return;
// update the interval, since the current interval is good to
// keep the port mapping.
mKeepAliveInterval = mMinInterval = mInterval;
@@ -826,7 +853,7 @@
} else {
// calculate the new interval and continue.
mInterval = (mMaxInterval + mMinInterval) / 2;
- mPassCounter = 0;
+ mPassCount = 0;
if (DEBUG) {
Log.d(TAG, "current interval: " + mKeepAliveInterval
+ ", test new interval: " + mInterval);
@@ -841,6 +868,13 @@
public void onError(int errorCode, String description) {
synchronized (SipService.this) {
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");
+ }
}
}
}
@@ -885,9 +919,15 @@
@Override
public void onResponse(boolean portChanged) {
synchronized (SipService.this) {
- // Start keep-alive interval measurement on the first successfully
- // kept-alive SipSessionGroup
- startPortMappingLifetimeMeasurement(mSession.getLocalProfile());
+ 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;
@@ -907,6 +947,7 @@
@Override
public void onError(int errorCode, String description) {
Log.e(TAG, "keepalive error: " + description);
+ onResponse(true); // re-register immediately
}
public void stop() {
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index cc3e4109..481e306 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -21,6 +21,8 @@
import gov.nist.javax.sip.header.SIPHeaderNames;
import gov.nist.javax.sip.header.ProxyAuthenticate;
import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.header.extensions.ReferredByHeader;
+import gov.nist.javax.sip.header.extensions.ReplacesHeader;
import gov.nist.javax.sip.message.SIPMessage;
import android.net.sip.ISipSession;
@@ -365,24 +367,85 @@
super(listener);
}
+ private SipSessionImpl createNewSession(RequestEvent event,
+ ISipSessionListener listener, ServerTransaction transaction)
+ throws SipException {
+ SipSessionImpl newSession = new SipSessionImpl(listener);
+ newSession.mServerTransaction = transaction;
+ newSession.mState = SipSession.State.INCOMING_CALL;
+ newSession.mDialog = newSession.mServerTransaction.getDialog();
+ newSession.mInviteReceived = event;
+ newSession.mPeerProfile = createPeerProfile(event.getRequest());
+ newSession.mPeerSessionDescription =
+ extractContent(event.getRequest());
+ return newSession;
+ }
+
+ private int processInviteWithReplaces(RequestEvent event,
+ ReplacesHeader replaces) {
+ String callId = replaces.getCallId();
+ SipSessionImpl session = mSessionMap.get(callId);
+ if (session == null) {
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+
+ Dialog dialog = session.mDialog;
+ if (dialog == null) return Response.DECLINE;
+
+ if (!dialog.getLocalTag().equals(replaces.getToTag()) ||
+ !dialog.getRemoteTag().equals(replaces.getFromTag())) {
+ // No match is found, returns 481.
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+
+ ReferredByHeader referredBy = (ReferredByHeader) event.getRequest()
+ .getHeader(ReferredByHeader.NAME);
+ if ((referredBy == null) ||
+ !dialog.getRemoteParty().equals(referredBy.getAddress())) {
+ return Response.CALL_OR_TRANSACTION_DOES_NOT_EXIST;
+ }
+ return Response.OK;
+ }
+
+ private void processNewInviteRequest(RequestEvent event)
+ throws SipException {
+ ReplacesHeader replaces = (ReplacesHeader) event.getRequest()
+ .getHeader(ReplacesHeader.NAME);
+ SipSessionImpl newSession = null;
+ if (replaces != null) {
+ int response = processInviteWithReplaces(event, replaces);
+ if (DEBUG) {
+ Log.v(TAG, "ReplacesHeader: " + replaces
+ + " response=" + response);
+ }
+ if (response == Response.OK) {
+ SipSessionImpl replacedSession =
+ mSessionMap.get(replaces.getCallId());
+ // got INVITE w/ replaces request.
+ newSession = createNewSession(event,
+ replacedSession.mProxy.getListener(),
+ mSipHelper.getServerTransaction(event));
+ newSession.mProxy.onCallTransferring(newSession,
+ newSession.mPeerSessionDescription);
+ } else {
+ mSipHelper.sendResponse(event, response);
+ }
+ } else {
+ // New Incoming call.
+ newSession = createNewSession(event, mProxy,
+ mSipHelper.sendRinging(event, generateTag()));
+ mProxy.onRinging(newSession, newSession.mPeerProfile,
+ newSession.mPeerSessionDescription);
+ }
+ if (newSession != null) addSipSession(newSession);
+ }
+
public boolean process(EventObject evt) throws SipException {
if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~ " + this + ": "
+ SipSession.State.toString(mState) + ": processing "
+ log(evt));
if (isRequestEvent(Request.INVITE, evt)) {
- RequestEvent event = (RequestEvent) evt;
- SipSessionImpl newSession = new SipSessionImpl(mProxy);
- newSession.mState = SipSession.State.INCOMING_CALL;
- newSession.mServerTransaction = mSipHelper.sendRinging(event,
- generateTag());
- newSession.mDialog = newSession.mServerTransaction.getDialog();
- newSession.mInviteReceived = event;
- newSession.mPeerProfile = createPeerProfile(event.getRequest());
- newSession.mPeerSessionDescription =
- extractContent(event.getRequest());
- addSipSession(newSession);
- mProxy.onRinging(newSession, newSession.mPeerProfile,
- newSession.mPeerSessionDescription);
+ processNewInviteRequest((RequestEvent) evt);
return true;
} else if (isRequestEvent(Request.OPTIONS, evt)) {
mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
@@ -1259,11 +1322,13 @@
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) {
@@ -1311,7 +1376,7 @@
if (DEBUG_PING) {
Log.d(TAG, "keepalive: " + mLocalProfile.getUriString()
- + " --> " + mPeerProfile);
+ + " --> " + mPeerProfile + ", interval=" + mInterval);
}
try {
sendKeepAlive();
diff --git a/voip/java/com/android/server/sip/SipSessionListenerProxy.java b/voip/java/com/android/server/sip/SipSessionListenerProxy.java
index f8be0a8..8655a3a 100644
--- a/voip/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/voip/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -110,6 +110,20 @@
});
}
+ public void onCallTransferring(final ISipSession newSession,
+ final String sessionDescription) {
+ if (mListener == null) return;
+ proxy(new Runnable() {
+ public void run() {
+ try {
+ mListener.onCallTransferring(newSession, sessionDescription);
+ } catch (Throwable t) {
+ handle(t, "onCallTransferring()");
+ }
+ }
+ });
+ }
+
public void onCallBusy(final ISipSession session) {
if (mListener == null) return;
proxy(new Runnable() {
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;