Merge "Move TLS behind hal."
diff --git a/api/current.txt b/api/current.txt
index 374c415..785eb47 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2180,7 +2180,7 @@
method public void closeContextMenu();
method public void closeOptionsMenu();
method public android.app.PendingIntent createPendingResult(int, android.content.Intent, int);
- method public final void dismissDialog(int);
+ method public final deprecated void dismissDialog(int);
method public boolean dispatchGenericMotionEvent(android.view.MotionEvent);
method public boolean dispatchKeyEvent(android.view.KeyEvent);
method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
@@ -2202,7 +2202,7 @@
method public android.view.View getCurrentFocus();
method public android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
- method public java.lang.Object getLastNonConfigurationInstance();
+ method public deprecated java.lang.Object getLastNonConfigurationInstance();
method public android.view.LayoutInflater getLayoutInflater();
method public android.app.LoaderManager getLoaderManager();
method public java.lang.String getLocalClassName();
@@ -2239,7 +2239,7 @@
method public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo);
method public java.lang.CharSequence onCreateDescription();
method protected deprecated android.app.Dialog onCreateDialog(int);
- method protected android.app.Dialog onCreateDialog(int, android.os.Bundle);
+ method protected deprecated android.app.Dialog onCreateDialog(int, android.os.Bundle);
method public boolean onCreateOptionsMenu(android.view.Menu);
method public boolean onCreatePanelMenu(int, android.view.Menu);
method public android.view.View onCreatePanelView(int);
@@ -2265,13 +2265,13 @@
method protected void onPostCreate(android.os.Bundle);
method protected void onPostResume();
method protected deprecated void onPrepareDialog(int, android.app.Dialog);
- method protected void onPrepareDialog(int, android.app.Dialog, android.os.Bundle);
+ method protected deprecated void onPrepareDialog(int, android.app.Dialog, android.os.Bundle);
method public boolean onPrepareOptionsMenu(android.view.Menu);
method public boolean onPreparePanel(int, android.view.View, android.view.Menu);
method protected void onRestart();
method protected void onRestoreInstanceState(android.os.Bundle);
method protected void onResume();
- method public java.lang.Object onRetainNonConfigurationInstance();
+ method public deprecated java.lang.Object onRetainNonConfigurationInstance();
method protected void onSaveInstanceState(android.os.Bundle);
method public boolean onSearchRequested();
method protected void onStart();
@@ -2289,7 +2289,7 @@
method public void overridePendingTransition(int, int);
method public void recreate();
method public void registerForContextMenu(android.view.View);
- method public final void removeDialog(int);
+ method public final deprecated void removeDialog(int);
method public final boolean requestWindowFeature(int);
method public final void runOnUiThread(java.lang.Runnable);
method public void setContentView(int);
@@ -2315,8 +2315,8 @@
method public void setTitleColor(int);
method public void setVisible(boolean);
method public final void setVolumeControlStream(int);
- method public final void showDialog(int);
- method public final boolean showDialog(int, android.os.Bundle);
+ method public final deprecated void showDialog(int);
+ method public final deprecated boolean showDialog(int, android.os.Bundle);
method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
method public void startActivityForResult(android.content.Intent, int);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
@@ -2851,6 +2851,7 @@
method public void onSaveInstanceState(android.os.Bundle);
method public void onStart();
method public void onStop();
+ method public void onViewCreated(android.view.View, android.os.Bundle);
method public void registerForContextMenu(android.view.View);
method public void setArguments(android.os.Bundle);
method public void setHasOptionsMenu(boolean);
@@ -3041,12 +3042,12 @@
}
public class KeyguardManager {
- method public void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
+ method public deprecated void exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult);
method public boolean inKeyguardRestrictedInputMode();
- method public android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String);
+ method public deprecated android.app.KeyguardManager.KeyguardLock newKeyguardLock(java.lang.String);
}
- public class KeyguardManager.KeyguardLock {
+ public deprecated class KeyguardManager.KeyguardLock {
method public void disableKeyguard();
method public void reenableKeyguard();
}
@@ -10012,6 +10013,7 @@
method public void setAudioStreamType(int);
method public void setAuxEffectSendLevel(float);
method public void setDataSource(android.content.Context, android.net.Uri) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
+ method public void setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String, java.lang.String>) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.SecurityException;
method public void setDataSource(java.lang.String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(java.io.FileDescriptor) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setDataSource(java.io.FileDescriptor, long, long) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -13169,6 +13171,7 @@
method public static void multiplyMM(float[], int, float[], int, float[], int);
method public static void multiplyMV(float[], int, float[], int, float[], int);
method public static void orthoM(float[], int, float, float, float, float, float, float);
+ method public static void perspectiveM(float[], int, float, float, float, float);
method public static void rotateM(float[], int, float[], int, float, float, float, float);
method public static void rotateM(float[], int, float, float, float, float);
method public static void scaleM(float[], int, float[], int, float, float, float);
@@ -13960,7 +13963,7 @@
field public static final int FULL_WAKE_LOCK = 26; // 0x1a
field public static final int ON_AFTER_RELEASE = 536870912; // 0x20000000
field public static final int PARTIAL_WAKE_LOCK = 1; // 0x1
- field public static final int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
+ field public static final deprecated int SCREEN_BRIGHT_WAKE_LOCK = 10; // 0xa
field public static final int SCREEN_DIM_WAKE_LOCK = 6; // 0x6
}
@@ -23128,11 +23131,11 @@
public class WebView.HitTestResult {
method public java.lang.String getExtra();
method public int getType();
- field public static final int ANCHOR_TYPE = 1; // 0x1
+ field public static final deprecated int ANCHOR_TYPE = 1; // 0x1
field public static final int EDIT_TEXT_TYPE = 9; // 0x9
field public static final int EMAIL_TYPE = 4; // 0x4
field public static final int GEO_TYPE = 3; // 0x3
- field public static final int IMAGE_ANCHOR_TYPE = 6; // 0x6
+ field public static final deprecated int IMAGE_ANCHOR_TYPE = 6; // 0x6
field public static final int IMAGE_TYPE = 5; // 0x5
field public static final int PHONE_TYPE = 2; // 0x2
field public static final int SRC_ANCHOR_TYPE = 7; // 0x7
@@ -24068,6 +24071,7 @@
ctor public LinearLayout(android.content.Context, android.util.AttributeSet);
ctor public LinearLayout(android.content.Context, android.util.AttributeSet, int);
method public int getBaselineAlignedChildIndex();
+ method public int getDividerPadding();
method public int getOrientation();
method public int getShowDividers();
method public float getWeightSum();
@@ -24077,6 +24081,7 @@
method public void setBaselineAligned(boolean);
method public void setBaselineAlignedChildIndex(int);
method public void setDividerDrawable(android.graphics.drawable.Drawable);
+ method public void setDividerPadding(int);
method public void setGravity(int);
method public void setHorizontalGravity(int);
method public void setMeasureWithLargestChildEnabled(boolean);
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 80ba1e9..9aa70a4 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -48,6 +48,11 @@
LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
return -errno;
}
+ if (chmod(pkgdir, 0751) < 0) {
+ LOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
+ unlink(pkgdir);
+ return -errno;
+ }
if (chown(pkgdir, uid, gid) < 0) {
LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
unlink(pkgdir);
@@ -58,6 +63,12 @@
unlink(pkgdir);
return -errno;
}
+ if (chmod(libdir, 0755) < 0) {
+ LOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
+ unlink(libdir);
+ unlink(pkgdir);
+ return -errno;
+ }
if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
LOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
unlink(libdir);
@@ -67,15 +78,15 @@
return 0;
}
-int uninstall(const char *pkgname)
+int uninstall(const char *pkgname, uid_t persona)
{
char pkgdir[PKG_PATH_MAX];
- if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0))
+ if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
return -1;
- /* delete contents AND directory, no exceptions */
- return delete_dir_contents(pkgdir, 1, 0);
+ /* delete contents AND directory, no exceptions */
+ return delete_dir_contents(pkgdir, 1, NULL);
}
int renamepkg(const char *oldpkgname, const char *newpkgname)
@@ -95,17 +106,48 @@
return 0;
}
-int delete_user_data(const char *pkgname)
+int delete_user_data(const char *pkgname, uid_t persona)
{
char pkgdir[PKG_PATH_MAX];
- if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0))
+ if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
return -1;
- /* delete contents, excluding "lib", but not the directory itself */
+ /* delete contents, excluding "lib", but not the directory itself */
return delete_dir_contents(pkgdir, 0, "lib");
}
+int make_user_data(const char *pkgname, uid_t uid, uid_t persona)
+{
+ char pkgdir[PKG_PATH_MAX];
+ char real_libdir[PKG_PATH_MAX];
+
+ // Create the data dir for the package
+ if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona)) {
+ return -1;
+ }
+ if (mkdir(pkgdir, 0751) < 0) {
+ LOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
+ return -errno;
+ }
+ if (chown(pkgdir, uid, uid) < 0) {
+ LOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
+ unlink(pkgdir);
+ return -errno;
+ }
+ return 0;
+}
+
+int delete_persona(uid_t persona)
+{
+ char pkgdir[PKG_PATH_MAX];
+
+ if (create_persona_path(pkgdir, persona))
+ return -1;
+
+ return delete_dir_contents(pkgdir, 1, NULL);
+}
+
int delete_cache(const char *pkgname)
{
char cachedir[PKG_PATH_MAX];
diff --git a/cmds/installd/installd.c b/cmds/installd/installd.c
index e0d0f97..c062d36 100644
--- a/cmds/installd/installd.c
+++ b/cmds/installd/installd.c
@@ -49,7 +49,7 @@
static int do_remove(char **arg, char reply[REPLY_MAX])
{
- return uninstall(arg[0]); /* pkgname */
+ return uninstall(arg[0], atoi(arg[1])); /* pkgname, userid */
}
static int do_rename(char **arg, char reply[REPLY_MAX])
@@ -92,7 +92,17 @@
static int do_rm_user_data(char **arg, char reply[REPLY_MAX])
{
- return delete_user_data(arg[0]); /* pkgname */
+ return delete_user_data(arg[0], atoi(arg[1])); /* pkgname, userid */
+}
+
+static int do_mk_user_data(char **arg, char reply[REPLY_MAX])
+{
+ return make_user_data(arg[0], atoi(arg[1]), atoi(arg[2])); /* pkgname, uid, userid */
+}
+
+static int do_rm_user(char **arg, char reply[REPLY_MAX])
+{
+ return delete_persona(atoi(arg[0])); /* userid */
}
static int do_movefiles(char **arg, char reply[REPLY_MAX])
@@ -122,16 +132,18 @@
{ "dexopt", 3, do_dexopt },
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
- { "remove", 1, do_remove },
+ { "remove", 2, do_remove },
{ "rename", 2, do_rename },
{ "freecache", 1, do_free_cache },
{ "rmcache", 1, do_rm_cache },
{ "protect", 2, do_protect },
{ "getsize", 3, do_get_size },
- { "rmuserdata", 1, do_rm_user_data },
+ { "rmuserdata", 2, do_rm_user_data },
{ "movefiles", 0, do_movefiles },
{ "linklib", 2, do_linklib },
{ "unlinklib", 1, do_unlinklib },
+ { "mkuserdata", 3, do_mk_user_data },
+ { "rmuser", 1, do_rm_user },
};
static int readx(int s, void *_buf, int count)
@@ -286,14 +298,50 @@
return -1;
}
+ // append "app/" to dirs[0]
+ char *system_app_path = build_string2(android_system_dirs.dirs[0].path, APP_SUBDIR);
+ android_system_dirs.dirs[0].path = system_app_path;
+ android_system_dirs.dirs[0].len = strlen(system_app_path);
+
// vendor
// TODO replace this with an environment variable (doesn't exist yet)
- android_system_dirs.dirs[1].path = "/vendor/";
+ android_system_dirs.dirs[1].path = "/vendor/app/";
android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path);
return 0;
}
+int initialize_directories() {
+ // /data/user
+ char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
+ // /data/data
+ char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
+ // /data/user/0
+ char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX,
+ "0");
+ int ret = -1;
+ if (user_data_dir != NULL && primary_data_dir != NULL && legacy_data_dir != NULL) {
+ ret = 0;
+ // Make the /data/user directory if necessary
+ if (access(user_data_dir, R_OK) < 0) {
+ if (mkdir(user_data_dir, 0755) < 0) {
+ return -1;
+ }
+ if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
+ return -1;
+ }
+ }
+ // Make the /data/user/0 symlink to /data/data if necessary
+ if (access(primary_data_dir, R_OK) < 0) {
+ ret = symlink(legacy_data_dir, primary_data_dir);
+ }
+ free(user_data_dir);
+ free(legacy_data_dir);
+ free(primary_data_dir);
+ }
+ return ret;
+}
+
int main(const int argc, const char *argv[]) {
char buf[BUFFER_MAX];
struct sockaddr addr;
@@ -305,6 +353,11 @@
exit(1);
}
+ if (initialize_directories() < 0) {
+ LOGE("Could not create directories; exiting.\n");
+ exit(1);
+ }
+
lsocket = android_get_control_socket(SOCKET_PATH);
if (lsocket < 0) {
LOGE("Failed to get socket from environment: %s\n", strerror(errno));
diff --git a/cmds/installd/installd.h b/cmds/installd/installd.h
index cbca135..e5f6739 100644
--- a/cmds/installd/installd.h
+++ b/cmds/installd/installd.h
@@ -102,6 +102,9 @@
const char *postfix,
uid_t persona);
+int create_persona_path(char path[PKG_PATH_MAX],
+ uid_t persona);
+
int is_valid_package_name(const char* pkgname);
int create_cache_path(char path[PKG_PATH_MAX], const char *src);
@@ -124,12 +127,17 @@
int append_and_increment(char** dst, const char* src, size_t* dst_size);
+char *build_string2(char *s1, char *s2);
+char *build_string3(char *s1, char *s2, char *s3);
+
/* commands.c */
int install(const char *pkgname, uid_t uid, gid_t gid);
-int uninstall(const char *pkgname);
+int uninstall(const char *pkgname, uid_t persona);
int renamepkg(const char *oldpkgname, const char *newpkgname);
-int delete_user_data(const char *pkgname);
+int delete_user_data(const char *pkgname, uid_t persona);
+int make_user_data(const char *pkgname, uid_t uid, uid_t persona);
+int delete_persona(uid_t persona);
int delete_cache(const char *pkgname);
int move_dex(const char *src, const char *dst);
int rm_dex(const char *path);
diff --git a/cmds/installd/utils.c b/cmds/installd/utils.c
index f37a6fb..3099b83 100644
--- a/cmds/installd/utils.c
+++ b/cmds/installd/utils.c
@@ -96,6 +96,46 @@
}
/**
+ * Create the path name for user data for a certain persona.
+ * Returns 0 on success, and -1 on failure.
+ */
+int create_persona_path(char path[PKG_PATH_MAX],
+ uid_t persona)
+{
+ size_t uid_len;
+ char* persona_prefix;
+ if (persona == 0) {
+ persona_prefix = PRIMARY_USER_PREFIX;
+ uid_len = 0;
+ } else {
+ persona_prefix = SECONDARY_USER_PREFIX;
+ uid_len = snprintf(NULL, 0, "%d", persona);
+ }
+
+ char *dst = path;
+ size_t dst_size = PKG_PATH_MAX;
+
+ if (append_and_increment(&dst, android_data_dir.path, &dst_size) < 0
+ || append_and_increment(&dst, persona_prefix, &dst_size) < 0) {
+ LOGE("Error building prefix for user path");
+ return -1;
+ }
+
+ if (persona != 0) {
+ if (dst_size < uid_len + 1) {
+ LOGE("Error building user path");
+ return -1;
+ }
+ int ret = snprintf(dst, dst_size, "%d", persona);
+ if (ret < 0 || (size_t) ret != uid_len) {
+ LOGE("Error appending persona id to path");
+ return -1;
+ }
+ }
+ return 0;
+}
+
+/**
* Checks whether the package name is valid. Returns -1 on error and
* 0 on success.
*/
@@ -408,3 +448,35 @@
*dst_size -= ret;
return 0;
}
+
+char *build_string2(char *s1, char *s2) {
+ if (s1 == NULL || s2 == NULL) return NULL;
+
+ int len_s1 = strlen(s1);
+ int len_s2 = strlen(s2);
+ int len = len_s1 + len_s2 + 1;
+ char *result = malloc(len);
+ if (result == NULL) return NULL;
+
+ strcpy(result, s1);
+ strcpy(result + len_s1, s2);
+
+ return result;
+}
+
+char *build_string3(char *s1, char *s2, char *s3) {
+ if (s1 == NULL || s2 == NULL || s3 == NULL) return NULL;
+
+ int len_s1 = strlen(s1);
+ int len_s2 = strlen(s2);
+ int len_s3 = strlen(s3);
+ int len = len_s1 + len_s2 + len_s3 + 1;
+ char *result = malloc(len);
+ if (result == NULL) return NULL;
+
+ strcpy(result, s1);
+ strcpy(result + len_s1, s2);
+ strcpy(result + len_s1 + len_s2, s3);
+
+ return result;
+}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index d058e38..78a450c 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -35,9 +35,9 @@
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.net.Uri;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.provider.Settings;
import java.io.File;
import java.lang.reflect.Field;
@@ -60,6 +60,7 @@
private static final String PM_NOT_RUNNING_ERR =
"Error: Could not access the Package Manager. Is the system running?";
+ private static final int ROOT_UID = 0;
public static void main(String[] args) {
new Pm().run(args);
@@ -127,6 +128,16 @@
return;
}
+ if ("createUser".equals(op)) {
+ runCreateUser();
+ return;
+ }
+
+ if ("removeUser".equals(op)) {
+ runRemoveUser();
+ return;
+ }
+
try {
if (args.length == 1) {
if (args[0].equalsIgnoreCase("-l")) {
@@ -763,6 +774,63 @@
}
}
+ public void runCreateUser() {
+ // Need to be run as root
+ if (Process.myUid() != ROOT_UID) {
+ System.err.println("Error: createUser must be run as root");
+ return;
+ }
+ String name;
+ String arg = nextArg();
+ if (arg == null) {
+ System.err.println("Error: no user name specified.");
+ showUsage();
+ return;
+ }
+ name = arg;
+ try {
+ if (mPm.createUser(name, 0) == null) {
+ System.err.println("Error: couldn't create user.");
+ showUsage();
+ }
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(PM_NOT_RUNNING_ERR);
+ }
+
+ }
+
+ public void runRemoveUser() {
+ // Need to be run as root
+ if (Process.myUid() != ROOT_UID) {
+ System.err.println("Error: removeUser must be run as root");
+ return;
+ }
+ int userId;
+ String arg = nextArg();
+ if (arg == null) {
+ System.err.println("Error: no user id specified.");
+ showUsage();
+ return;
+ }
+ try {
+ userId = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ System.err.println("Error: user id has to be a number.");
+ showUsage();
+ return;
+ }
+ try {
+ if (!mPm.removeUser(userId)) {
+ System.err.println("Error: couldn't remove user.");
+ showUsage();
+ }
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(PM_NOT_RUNNING_ERR);
+ }
+ }
+
class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
boolean finished;
boolean result;
@@ -1006,6 +1074,8 @@
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 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:");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ed39457..389d4856 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1437,6 +1437,10 @@
}
/**
+ * @deprecated Use the new {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Retrieve the non-configuration instance data that was previously
* returned by {@link #onRetainNonConfigurationInstance()}. This will
* be available from the initial {@link #onCreate} and
@@ -1453,12 +1457,17 @@
* @return Returns the object previously returned by
* {@link #onRetainNonConfigurationInstance()}.
*/
+ @Deprecated
public Object getLastNonConfigurationInstance() {
return mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.activity : null;
}
/**
+ * @deprecated Use the new {@link Fragment} API
+ * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Called by the system, as part of destroying an
* activity due to a configuration change, when it is known that a new
* instance will immediately be created for the new configuration. You
@@ -1674,6 +1683,10 @@
}
/**
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* This method allows the activity to take care of managing the given
* {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
* That is, when the activity is stopped it will automatically call
@@ -1689,8 +1702,6 @@
*
* @see #managedQuery(android.net.Uri , String[], String, String[], String)
* @see #stopManagingCursor
- *
- * @deprecated Use {@link CursorLoader} instead.
*/
@Deprecated
public void startManagingCursor(Cursor c) {
@@ -1700,6 +1711,10 @@
}
/**
+ * @deprecated Use the new {@link android.content.CursorLoader} class with
+ * {@link LoaderManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Given a Cursor that was previously given to
* {@link #startManagingCursor}, stop the activity's management of that
* cursor.
@@ -1707,8 +1722,6 @@
* @param c The Cursor that was being managed.
*
* @see #startManagingCursor
- *
- * @deprecated Use {@link CursorLoader} instead.
*/
@Deprecated
public void stopManagingCursor(Cursor c) {
@@ -2714,6 +2727,10 @@
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Callback for creating dialogs that are managed (saved and restored) for you
* by the activity. The default implementation calls through to
* {@link #onCreateDialog(int)} for compatibility.
@@ -2742,6 +2759,7 @@
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
protected Dialog onCreateDialog(int id, Bundle args) {
return onCreateDialog(id);
}
@@ -2756,6 +2774,10 @@
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Provides an opportunity to prepare a managed dialog before it is being
* shown. The default implementation calls through to
* {@link #onPrepareDialog(int, Dialog)} for compatibility.
@@ -2775,20 +2797,30 @@
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
onPrepareDialog(id, dialog);
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Simple version of {@link #showDialog(int, Bundle)} that does not
* take any arguments. Simply calls {@link #showDialog(int, Bundle)}
* with null arguments.
*/
+ @Deprecated
public final void showDialog(int id) {
showDialog(id, null);
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Show a dialog managed by this activity. A call to {@link #onCreateDialog(int, Bundle)}
* will be made with the same id the first time this is called for a given
* id. From thereafter, the dialog will be automatically saved and restored.
@@ -2814,6 +2846,7 @@
* @see #dismissDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
public final boolean showDialog(int id, Bundle args) {
if (mManagedDialogs == null) {
mManagedDialogs = new SparseArray<ManagedDialog>();
@@ -2835,6 +2868,10 @@
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
*
* @param id The id of the managed dialog.
@@ -2847,6 +2884,7 @@
* @see #showDialog(int)
* @see #removeDialog(int)
*/
+ @Deprecated
public final void dismissDialog(int id) {
if (mManagedDialogs == null) {
throw missingDialog(id);
@@ -2869,6 +2907,10 @@
}
/**
+ * @deprecated Use the new {@link DialogFragment} class with
+ * {@link FragmentManager} instead; this is also
+ * available on older platforms through the Android compatibility package.
+ *
* Removes any internal references to a dialog managed by this Activity.
* If the dialog is showing, it will dismiss it as part of the clean up.
*
@@ -2886,6 +2928,7 @@
* @see #showDialog(int)
* @see #dismissDialog(int)
*/
+ @Deprecated
public final void removeDialog(int id) {
if (mManagedDialogs != null) {
final ManagedDialog md = mManagedDialogs.get(id);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index ef8ba8e..85918cf 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1113,7 +1113,11 @@
*/
@Override
public UserInfo createUser(String name, int flags) {
- // TODO
+ try {
+ return mPM.createUser(name, flags);
+ } catch (RemoteException e) {
+ // Should never happen!
+ }
return null;
}
@@ -1136,8 +1140,11 @@
*/
@Override
public boolean removeUser(int id) {
- // TODO:
- return false;
+ try {
+ return mPM.removeUser(id);
+ } catch (RemoteException e) {
+ return false;
+ }
}
/**
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a528a1aa..6f0bbd7 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -164,11 +164,21 @@
*
* <p>Topics covered here:
* <ol>
+ * <li><a href="#OlderPlatforms">Older Platforms</a>
* <li><a href="#Lifecycle">Lifecycle</a>
* <li><a href="#Layout">Layout</a>
* <li><a href="#BackStack">Back Stack</a>
* </ol>
*
+ * <a name="OlderPlatforms"></a>
+ * <h3>Older Platforms</h3>
+ *
+ * While the Fragment API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms. See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
* <a name="Lifecycle"></a>
* <h3>Lifecycle</h3>
*
@@ -326,8 +336,9 @@
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
- static final int STARTED = 3; // Created and started, not resumed.
- static final int RESUMED = 4; // Created started and resumed.
+ static final int STOPPED = 3; // Fully created, not started.
+ static final int STARTED = 4; // Created and started, not resumed.
+ static final int RESUMED = 5; // Created started and resumed.
int mState = INITIALIZING;
@@ -959,6 +970,19 @@
}
/**
+ * Called immediately after {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}
+ * has returned, but before any saved state has been restored in to the view.
+ * This gives subclasses a chance to initialize themselves once
+ * they know their view hierarchy has been completely created. The fragment's
+ * view hierarchy is not however attached to its parent at this point.
+ * @param view The View returned by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ }
+
+ /**
* Called to have the fragment instantiate its user interface view.
* This is optional, and non-graphical fragments can return null (which
* is the default implementation). This will be called between
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 1eaca3b..0da656f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -714,13 +714,14 @@
null, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
+ if (f.mHidden) f.mView.setVisibility(View.GONE);
f.restoreViewState();
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
- if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
+ if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
@@ -744,9 +745,10 @@
anim.start();
}
container.addView(f.mView);
- f.restoreViewState();
}
- if (f.mHidden) f.mView.setVisibility(View.GONE);
+ if (f.mHidden) f.mView.setVisibility(View.GONE);
+ f.restoreViewState();
+ f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
@@ -756,10 +758,13 @@
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onActivityCreated()");
}
+ if (f.mView != null) {
+ }
f.mSavedFragmentState = null;
}
case Fragment.ACTIVITY_CREATED:
- if (newState > Fragment.ACTIVITY_CREATED) {
+ case Fragment.STOPPED:
+ if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
f.mCalled = false;
f.onStart();
@@ -803,9 +808,10 @@
+ " did not call through to super.onStop()");
}
}
+ case Fragment.STOPPED:
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
- if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
+ if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f);
if (f.mView != null) {
// Need to save the current view state if not
// done already.
@@ -1631,7 +1637,7 @@
}
public void dispatchStop() {
- moveToState(Fragment.ACTIVITY_CREATED, false);
+ moveToState(Fragment.STOPPED, false);
}
public void dispatchDestroy() {
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index a601fbf..0fe7b5c 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -35,6 +35,12 @@
private IWindowManager mWM;
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Handle returned by {@link KeyguardManager#newKeyguardLock} that allows
* you to disable / reenable the keyguard.
*/
@@ -103,6 +109,12 @@
}
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Enables you to lock or unlock the keyboard. Get an instance of this class by
* calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
* This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}.
@@ -112,6 +124,7 @@
* @return A {@link KeyguardLock} handle to use to disable and reenable the
* keyguard.
*/
+ @Deprecated
public KeyguardLock newKeyguardLock(String tag) {
return new KeyguardLock(tag);
}
@@ -168,6 +181,12 @@
}
/**
+ * @deprecated Use {@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
+ * and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
+ * instead; this allows you to seamlessly hide the keyguard as your application
+ * moves in and out of the foreground and does not require that any special
+ * permissions be requested.
+ *
* Exit the keyguard securely. The use case for this api is that, after
* disabling the keyguard, your app, which was granted permission to
* disable the keyguard and show a limited amount of information deemed
@@ -181,6 +200,7 @@
* it is safe to launch anything that would normally be considered safe
* once the user has gotten past the keyguard.
*/
+ @Deprecated
public void exitKeyguardSecurely(final OnKeyguardExitResult callback) {
try {
mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 6e2f4b6..a5ee26c 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -195,11 +195,11 @@
}
/**
- * Attach to list view once Fragment is ready to run.
+ * Attach to list view once the view hierarchy has been created.
*/
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
ensureList();
}
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 1ee386d..164141c 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -34,12 +34,18 @@
* {@link android.content.CursorLoader}, however applications are free to write
* their own loaders for loading other types of data.
*
+ * While the LoaderManager API was introduced in
+ * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, a version of the API
+ * is also available for use on older platforms. See the blog post
+ * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
+ * Fragments For All</a> for more details.
+ *
* <p>As an example, here is the full implementation of a {@link Fragment}
* that displays a {@link android.widget.ListView} containing the results of
* a query against the contacts content provider. It uses a
* {@link android.content.CursorLoader} to manage the query on the provider.
*
- * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentListCursorLoader.java
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java
* fragment_cursor}
*/
public abstract class LoaderManager {
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index f4693c2..56f236d 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -26,8 +26,8 @@
import android.server.BluetoothService;
import android.util.Log;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import java.util.Set;
@@ -57,7 +57,7 @@
* Todo(): Write tests for this class, when the Android Mock support is completed.
* @hide
*/
-public final class BluetoothDeviceProfileState extends HierarchicalStateMachine {
+public final class BluetoothDeviceProfileState extends StateMachine {
private static final String TAG = "BluetoothDeviceProfileState";
private static final boolean DBG = false;
@@ -235,16 +235,16 @@
}
}
- private class BondedDevice extends HierarchicalState {
+ private class BondedDevice extends State {
@Override
- protected void enter() {
+ public void enter() {
Log.i(TAG, "Entering ACL Connected state with: " + getCurrentMessage().what);
Message m = new Message();
m.copyFrom(getCurrentMessage());
sendMessageAtFrontOfQueue(m);
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("ACL Connected State -> Processing Message: " + message.what);
switch(message.what) {
case CONNECT_HFP_OUTGOING:
@@ -353,12 +353,12 @@
}
}
- private class OutgoingHandsfree extends HierarchicalState {
+ private class OutgoingHandsfree extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
Log.i(TAG, "Entering OutgoingHandsfree state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HFP_OUTGOING &&
@@ -374,7 +374,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("OutgoingHandsfree State -> Processing Message: " + message.what);
Message deferMsg = new Message();
int command = message.what;
@@ -466,12 +466,12 @@
}
}
- private class IncomingHandsfree extends HierarchicalState {
+ private class IncomingHandsfree extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
Log.i(TAG, "Entering IncomingHandsfree state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HFP_INCOMING &&
@@ -487,7 +487,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("IncomingHandsfree State -> Processing Message: " + message.what);
switch(message.what) {
case CONNECT_HFP_OUTGOING:
@@ -546,12 +546,12 @@
}
}
- private class OutgoingA2dp extends HierarchicalState {
+ private class OutgoingA2dp extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
Log.i(TAG, "Entering OutgoingA2dp state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_A2DP_OUTGOING &&
@@ -567,7 +567,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("OutgoingA2dp State->Processing Message: " + message.what);
Message deferMsg = new Message();
switch(message.what) {
@@ -656,12 +656,12 @@
}
}
- private class IncomingA2dp extends HierarchicalState {
+ private class IncomingA2dp extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
Log.i(TAG, "Entering IncomingA2dp state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_A2DP_INCOMING &&
@@ -677,7 +677,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("IncomingA2dp State->Processing Message: " + message.what);
switch(message.what) {
case CONNECT_HFP_OUTGOING:
@@ -734,12 +734,12 @@
}
- private class OutgoingHid extends HierarchicalState {
+ private class OutgoingHid extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
log("Entering OutgoingHid state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HID_OUTGOING &&
@@ -751,7 +751,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("OutgoingHid State->Processing Message: " + message.what);
Message deferMsg = new Message();
switch(message.what) {
@@ -815,12 +815,12 @@
}
}
- private class IncomingHid extends HierarchicalState {
+ private class IncomingHid extends State {
private boolean mStatus = false;
private int mCommand;
@Override
- protected void enter() {
+ public void enter() {
log("Entering IncomingHid state with: " + getCurrentMessage().what);
mCommand = getCurrentMessage().what;
if (mCommand != CONNECT_HID_INCOMING &&
@@ -832,7 +832,7 @@
}
@Override
- protected boolean processMessage(Message message) {
+ public boolean processMessage(Message message) {
log("IncomingHid State->Processing Message: " + message.what);
Message deferMsg = new Message();
switch(message.what) {
diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java
index 18060a0..98afdb8 100644
--- a/core/java/android/bluetooth/BluetoothProfileState.java
+++ b/core/java/android/bluetooth/BluetoothProfileState.java
@@ -22,8 +22,8 @@
import android.os.Message;
import android.util.Log;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
/**
* This state machine is used to serialize the connections
@@ -39,7 +39,7 @@
* @hide
*/
-public class BluetoothProfileState extends HierarchicalStateMachine {
+public class BluetoothProfileState extends StateMachine {
private static final boolean DBG = true;
private static final String TAG = "BluetoothProfileState";
@@ -101,15 +101,15 @@
context.registerReceiver(mBroadcastReceiver, filter);
}
- private class StableState extends HierarchicalState {
+ private class StableState extends State {
@Override
- protected void enter() {
+ public void enter() {
log("Entering Stable State");
mPendingDevice = null;
}
@Override
- protected boolean processMessage(Message msg) {
+ public boolean processMessage(Message msg) {
if (msg.what != TRANSITION_TO_STABLE) {
transitionTo(mPendingCommandState);
}
@@ -117,15 +117,15 @@
}
}
- private class PendingCommandState extends HierarchicalState {
+ private class PendingCommandState extends State {
@Override
- protected void enter() {
+ public void enter() {
log("Entering PendingCommandState State");
dispatchMessage(getCurrentMessage());
}
@Override
- protected boolean processMessage(Message msg) {
+ public boolean processMessage(Message msg) {
if (msg.what == TRANSITION_TO_STABLE) {
transitionTo(mStableState);
} else {
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 383cb6b..0b54396 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -27,7 +27,24 @@
import java.util.concurrent.CountDownLatch;
/**
- * Abstract Loader that provides an {@link AsyncTask} to do the work.
+ * Abstract Loader that provides an {@link AsyncTask} to do the work. See
+ * {@link Loader} and {@link android.app.LoaderManager} for more details.
+ *
+ * <p>Here is an example implementation of an AsyncTaskLoader subclass that
+ * loads the currently installed applications from the package manager. This
+ * implementation takes care of retrieving the application labels and sorting
+ * its result set from them, monitoring for changes to the installed
+ * applications, and rebuilding the list when a change in configuration requires
+ * this (such as a locale change).
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * loader}
+ *
+ * <p>An example implementation of a fragment that uses the above loader to show
+ * the currently installed applications in a list is below.
+ *
+ * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java
+ * fragment}
*
* @param <D> the data type to be loaded.
*/
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index a9d6117..4e70b74 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -26,7 +26,7 @@
/**
* An abstract class that performs asynchronous loading of data. While Loaders are active
* they should monitor the source of their data and deliver new results when the contents
- * change.
+ * change. See {@link android.app.LoaderManager} for more detail.
*
* <p><b>Note on threading:</b> Clients of loaders should as a rule perform
* any calls on to a Loader from the main thread of their process (that is,
@@ -36,7 +36,10 @@
* be done on the main thread.</p>
*
* <p>Subclasses generally must implement at least {@link #onStartLoading()},
- * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.
+ * {@link #onStopLoading()}, {@link #onForceLoad()}, and {@link #onReset()}.</p>
+ *
+ * <p>Most implementations should not derive directly from this class, but
+ * instead inherit from {@link AsyncTaskLoader}.</p>
*
* @param <D> The result returned when the load is complete
*/
@@ -76,8 +79,12 @@
}
/**
- * Stores away the application context associated with context. Since Loaders can be used
- * across multiple activities it's dangerous to store the context directly.
+ * Stores away the application context associated with context.
+ * Since Loaders can be used across multiple activities it's dangerous to
+ * store the context directly; always use {@link #getContext()} to retrieve
+ * the Loader's Context, don't use the constructor argument directly.
+ * The Context returned by {@link #getContext} is safe to use across
+ * Activity instances.
*
* @param context used to retrieve the application context.
*/
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index fbf8f92..11cd446 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -35,6 +35,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.content.IntentSender;
@@ -329,4 +330,7 @@
boolean setInstallLocation(int loc);
int getInstallLocation();
+
+ UserInfo createUser(in String name, int flags);
+ boolean removeUser(int userId);
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 99c4c7f..ff817c1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -662,10 +662,15 @@
public static final int MOVE_EXTERNAL_MEDIA = 0x00000002;
/**
- * Feature for {@link #getSystemAvailableFeatures} and
- * {@link #hasSystemFeature}: The device's audio pipeline is low-latency,
- * more suitable for audio applications sensitive to delays or lag in
- * sound input or output.
+ * Range of IDs allocated for a user.
+ * @hide
+ */
+ public static final int PER_USER_RANGE = 100000;
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device's
+ * audio pipeline is low-latency, more suitable for audio applications sensitive to delays or
+ * lag in sound input or output.
*/
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
@@ -2387,4 +2392,37 @@
* @hide
*/
public abstract void updateUserFlags(int id, int flags);
+
+ /**
+ * Checks to see if the user id is the same for the two uids, i.e., they belong to the same
+ * user.
+ * @hide
+ */
+ public static boolean isSameUser(int uid1, int uid2) {
+ return getUserId(uid1) == getUserId(uid2);
+ }
+
+ /**
+ * Returns the user id for a given uid.
+ * @hide
+ */
+ public static int getUserId(int uid) {
+ return uid / PER_USER_RANGE;
+ }
+
+ /**
+ * Returns the uid that is composed from the userId and the appId.
+ * @hide
+ */
+ public static int getUid(int userId, int appId) {
+ return userId * PER_USER_RANGE + (appId % PER_USER_RANGE);
+ }
+
+ /**
+ * Returns the app id (or base uid) for a given uid, stripping out the user id from it.
+ * @hide
+ */
+ public static int getAppId(int uid) {
+ return uid % PER_USER_RANGE;
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 54a8842..564f4f4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -24,6 +24,7 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
diff --git a/core/java/android/content/pm/UserInfo.aidl b/core/java/android/content/pm/UserInfo.aidl
new file mode 100644
index 0000000..2e7cb8f
--- /dev/null
+++ b/core/java/android/content/pm/UserInfo.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** 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.content.pm;
+
+parcelable UserInfo;
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 3704d3a..ba5331c 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -74,8 +74,7 @@
@Override
public String toString() {
- return "UserInfo{"
- + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
+ return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
}
public int describeContents() {
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index b9dfd72..ad48786 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -26,7 +26,11 @@
* This class describes all device configuration information that can
* impact the resources the application retrieves. This includes both
* user-specified configuration options (locale and scaling) as well
- * as dynamic device configuration (various types of input devices).
+ * as device configurations (such as input modes, screen size and screen orientation).
+ * <p>You can acquire this object from {@link Resources}, using {@link
+ * Resources#getConfiguration}. Thus, from an activity, you can get it by chaining the request
+ * with {@link android.app.Activity#getResources}:</p>
+ * <pre>Configuration config = getResources().getConfiguration();</pre>
*/
public final class Configuration implements Parcelable, Comparable<Configuration> {
/**
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index ab5c78a..dfc70ef 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -142,7 +142,8 @@
private int mPreviewTextSizeLarge;
private int mPreviewOffset;
private int mPreviewHeight;
- private int[] mOffsetInWindow;
+ // Working variable
+ private final int[] mCoordinates = new int[2];
private PopupWindow mPopupKeyboard;
private View mMiniKeyboardContainer;
@@ -152,7 +153,6 @@
private int mMiniKeyboardOffsetX;
private int mMiniKeyboardOffsetY;
private Map<Key,View> mMiniKeyboardCache;
- private int[] mWindowOffset;
private Key[] mKeys;
/** Listener for {@link OnKeyboardActionListener}. */
@@ -905,23 +905,19 @@
mPopupPreviewY = - mPreviewText.getMeasuredHeight();
}
mHandler.removeMessages(MSG_REMOVE_PREVIEW);
- if (mOffsetInWindow == null) {
- mOffsetInWindow = new int[2];
- getLocationInWindow(mOffsetInWindow);
- mOffsetInWindow[0] += mMiniKeyboardOffsetX; // Offset may be zero
- mOffsetInWindow[1] += mMiniKeyboardOffsetY; // Offset may be zero
- int[] mWindowLocation = new int[2];
- getLocationOnScreen(mWindowLocation);
- mWindowY = mWindowLocation[1];
- }
+ getLocationInWindow(mCoordinates);
+ mCoordinates[0] += mMiniKeyboardOffsetX; // Offset may be zero
+ mCoordinates[1] += mMiniKeyboardOffsetY; // Offset may be zero
+
// Set the preview background state
mPreviewText.getBackground().setState(
key.popupResId != 0 ? LONG_PRESSABLE_STATE_SET : EMPTY_STATE_SET);
- mPopupPreviewX += mOffsetInWindow[0];
- mPopupPreviewY += mOffsetInWindow[1];
+ mPopupPreviewX += mCoordinates[0];
+ mPopupPreviewY += mCoordinates[1];
// If the popup cannot be shown above the key, put it on the side
- if (mPopupPreviewY + mWindowY < 0) {
+ getLocationOnScreen(mCoordinates);
+ if (mPopupPreviewY + mCoordinates[1] < 0) {
// If the key you're pressing is on the left side of the keyboard, show the popup on
// the right, offset by enough to see at least one key to the left/right.
if (key.x + key.width <= getWidth() / 2) {
@@ -1057,16 +1053,13 @@
mMiniKeyboard = (KeyboardView) mMiniKeyboardContainer.findViewById(
com.android.internal.R.id.keyboardView);
}
- if (mWindowOffset == null) {
- mWindowOffset = new int[2];
- getLocationInWindow(mWindowOffset);
- }
+ getLocationInWindow(mCoordinates);
mPopupX = popupKey.x + mPaddingLeft;
mPopupY = popupKey.y + mPaddingTop;
mPopupX = mPopupX + popupKey.width - mMiniKeyboardContainer.getMeasuredWidth();
mPopupY = mPopupY - mMiniKeyboardContainer.getMeasuredHeight();
- final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mWindowOffset[0];
- final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mWindowOffset[1];
+ final int x = mPopupX + mMiniKeyboardContainer.getPaddingRight() + mCoordinates[0];
+ final int y = mPopupY + mMiniKeyboardContainer.getPaddingBottom() + mCoordinates[1];
mMiniKeyboard.setPopupOffset(x < 0 ? 0 : x, y);
mMiniKeyboard.setShifted(isShifted());
mPopupKeyboard.setContentView(mMiniKeyboardContainer);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 57fdb0c..c830f7a 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -131,14 +131,25 @@
/**
* Wake lock that ensures that the screen and keyboard are on at
* full brightness.
+ *
+ * <p class="note">Most applications should strongly consider using
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON}.
+ * This window flag will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.</p>
*/
public static final int FULL_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT
| WAKE_BIT_KEYBOARD_BRIGHT;
/**
+ * @deprecated Most applications should use
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
+ * of this type of wake lock, as it will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.
+ *
* Wake lock that ensures that the screen is on at full brightness;
* the keyboard backlight will be allowed to go off.
*/
+ @Deprecated
public static final int SCREEN_BRIGHT_WAKE_LOCK = WAKE_BIT_CPU_WEAK | WAKE_BIT_SCREEN_BRIGHT;
/**
@@ -384,6 +395,11 @@
*wl.release();
* }
*
+ * <p class="note">If using this to keep the screen on, you should strongly consider using
+ * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead.
+ * This window flag will be correctly managed by the platform
+ * as the user moves between applications and doesn't require a special permission.</p>
+ *
* @param flags Combination of flag values defining the requested behavior of the WakeLock.
* @param tag Your class name (or other tag) for debugging purposes.
*
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index de71763..309a5ec 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -98,6 +98,8 @@
public static final String SYNC4 = "sync4";
/** Generic column for use by sync adapters. */
public static final String SYNC5 = "sync5";
+ /** Generic column for use by sync adapters. */
+ public static final String SYNC6 = "sync6";
}
/**
@@ -135,15 +137,6 @@
public static final String _SYNC_VERSION = "_sync_version";
/**
- * For use by sync adapter at its discretion; not modified by CalendarProvider
- * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
- * schema change.
- * TODO Replace this with something more general in the future.
- * <P>Type: INTEGER (long)</P>
- */
- public static final String _SYNC_DATA = "_sync_local_id";
-
- /**
* Used only in persistent providers, and only during merging.
* <P>Type: INTEGER (long)</P>
*/
@@ -212,7 +205,7 @@
* Is the calendar selected to be displayed?
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String SELECTED = "selected";
+ public static final String VISIBLE = "visible";
/**
* The timezone the calendar's events occurs in
@@ -288,29 +281,32 @@
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_ID);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_VERSION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_TIME);
- DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, _SYNC_DATA);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_DIRTY);
- DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, _SYNC_MARK);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC1);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC2);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC3);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC4);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC5);
+ DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.SYNC6);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.NAME);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Calendars.DISPLAY_NAME);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, Calendars.COLOR);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, ACCESS_LEVEL);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SELECTED);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, VISIBLE);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, SYNC_EVENTS);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, Calendars.LOCATION);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv, TIMEZONE);
DatabaseUtils.cursorStringToContentValuesIfPresent(cursor, cv,
Calendars.OWNER_ACCOUNT);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
- Calendars.ORGANIZER_CAN_RESPOND);
+ Calendars.CAN_ORGANIZER_RESPOND);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.CAN_MODIFY_TIME_ZONE);
+ DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv,
+ Calendars.MAX_REMINDERS);
DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, DELETED);
@@ -432,7 +428,19 @@
* organizer should not be shown by the UI. Defaults to 1
* <P>Type: INTEGER (boolean)</P>
*/
- public static final String ORGANIZER_CAN_RESPOND = "organizerCanRespond";
+ public static final String CAN_ORGANIZER_RESPOND = "canOrganizerRespond";
+
+ /**
+ * Can the organizer modify the time zone of the event?
+ * <P>Type: INTEGER (boolean)</P>
+ */
+ public static final String CAN_MODIFY_TIME_ZONE = "canModifyTimeZone";
+
+ /**
+ * The maximum number of reminders allowed for an event.
+ * <P>Type: INTEGER</P>
+ */
+ public static final String MAX_REMINDERS = "maxReminders";
}
/**
@@ -505,6 +513,15 @@
*/
public interface EventsColumns {
/**
+ * For use by sync adapter at its discretion; not modified by CalendarProvider
+ * Note that this column was formerly named _SYNC_LOCAL_ID. We are using it to avoid a
+ * schema change.
+ * TODO Replace this with something more general in the future.
+ * <P>Type: INTEGER (long)</P>
+ */
+ public static final String _SYNC_DATA = "_sync_local_id";
+
+ /**
* The calendar the event belongs to
* <P>Type: INTEGER (foreign key to the Calendars table)</P>
*/
@@ -1011,7 +1028,7 @@
*/
public static final class Instances implements BaseColumns, EventsColumns, CalendarsColumns {
- private static final String WHERE_CALENDARS_SELECTED = Calendars.SELECTED + "=1";
+ private static final String WHERE_CALENDARS_SELECTED = Calendars.VISIBLE + "=1";
public static final Cursor query(ContentResolver cr, String[] projection,
long begin, long end) {
diff --git a/core/java/android/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java
index 6a9b2dc..7efc264 100644
--- a/core/java/android/speech/tts/FileSynthesisRequest.java
+++ b/core/java/android/speech/tts/FileSynthesisRequest.java
@@ -45,6 +45,7 @@
private int mChannelCount;
private RandomAccessFile mFile;
private boolean mStopped = false;
+ private boolean mDone = false;
FileSynthesisRequest(String text, File fileName) {
super(text);
@@ -89,6 +90,11 @@
}
@Override
+ boolean isDone() {
+ return mDone;
+ }
+
+ @Override
public int start(int sampleRateInHz, int audioFormat, int channelCount) {
if (DBG) {
Log.d(TAG, "FileSynthesisRequest.start(" + sampleRateInHz + "," + audioFormat
@@ -164,6 +170,7 @@
mFile.write(
makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
closeFile();
+ mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
@@ -174,6 +181,14 @@
}
@Override
+ public void error() {
+ if (DBG) Log.d(TAG, "FileSynthesisRequest.error()");
+ synchronized (mStateLock) {
+ cleanUp();
+ }
+ }
+
+ @Override
public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
byte[] buffer, int offset, int length) {
synchronized (mStateLock) {
@@ -187,9 +202,11 @@
out = new FileOutputStream(mFileName);
out.write(makeWavHeader(sampleRateInHz, audioFormat, channelCount, length));
out.write(buffer, offset, length);
+ mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
Log.e(TAG, "Failed to write to " + mFileName + ": " + ex);
+ mFileName.delete();
return TextToSpeech.ERROR;
} finally {
try {
diff --git a/core/java/android/speech/tts/PlaybackSynthesisRequest.java b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
index 2267015..dc5ff70 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java
@@ -50,6 +50,7 @@
private final Object mStateLock = new Object();
private AudioTrack mAudioTrack = null;
private boolean mStopped = false;
+ private boolean mDone = false;
PlaybackSynthesisRequest(String text, int streamType, float volume, float pan) {
super(text);
@@ -72,7 +73,6 @@
if (mAudioTrack != null) {
mAudioTrack.flush();
mAudioTrack.stop();
- // TODO: do we need to wait for playback to finish before releasing?
mAudioTrack.release();
mAudioTrack = null;
}
@@ -85,6 +85,11 @@
return MIN_AUDIO_BUFFER_SIZE;
}
+ @Override
+ boolean isDone() {
+ return mDone;
+ }
+
// TODO: add a thread that writes to the AudioTrack?
@Override
public int start(int sampleRateInHz, int audioFormat, int channelCount) {
@@ -104,8 +109,7 @@
return TextToSpeech.ERROR;
}
- mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
- AudioTrack.MODE_STREAM);
+ mAudioTrack = createStreamingAudioTrack(sampleRateInHz, audioFormat, channelCount);
if (mAudioTrack == null) {
return TextToSpeech.ERROR;
}
@@ -183,12 +187,21 @@
Log.e(TAG, "done(): Not started");
return TextToSpeech.ERROR;
}
+ mDone = true;
cleanUp();
}
return TextToSpeech.SUCCESS;
}
@Override
+ public void error() {
+ if (DBG) Log.d(TAG, "error()");
+ synchronized (mStateLock) {
+ cleanUp();
+ }
+ }
+
+ @Override
public int completeAudioAvailable(int sampleRateInHz, int audioFormat, int channelCount,
byte[] buffer, int offset, int length) {
if (DBG) {
@@ -208,15 +221,32 @@
return TextToSpeech.ERROR;
}
- mAudioTrack = createAudioTrack(sampleRateInHz, audioFormat, channelCount,
- AudioTrack.MODE_STATIC);
+ int channelConfig = getChannelConfig(channelCount);
+ if (channelConfig < 0) {
+ Log.e(TAG, "Unsupported number of channels :" + channelCount);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+ int bytesPerFrame = getBytesPerFrame(audioFormat);
+ if (bytesPerFrame < 0) {
+ Log.e(TAG, "Unsupported audio format :" + audioFormat);
+ cleanUp();
+ return TextToSpeech.ERROR;
+ }
+
+ mAudioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
+ audioFormat, buffer.length, AudioTrack.MODE_STATIC);
if (mAudioTrack == null) {
return TextToSpeech.ERROR;
}
try {
mAudioTrack.write(buffer, offset, length);
+ setupVolume(mAudioTrack, mVolume, mPan);
mAudioTrack.play();
+ blockUntilDone(mAudioTrack, bytesPerFrame, length);
+ mDone = true;
+ if (DBG) Log.d(TAG, "Wrote data to audio track succesfully : " + length);
} catch (IllegalStateException ex) {
Log.e(TAG, "Playback error", ex);
return TextToSpeech.ERROR;
@@ -228,15 +258,48 @@
return TextToSpeech.SUCCESS;
}
- private AudioTrack createAudioTrack(int sampleRateInHz, int audioFormat, int channelCount,
- int mode) {
- int channelConfig;
+ private void blockUntilDone(AudioTrack audioTrack, int bytesPerFrame, int length) {
+ int lengthInFrames = length / bytesPerFrame;
+ int currentPosition = 0;
+ while ((currentPosition = audioTrack.getPlaybackHeadPosition()) < lengthInFrames) {
+ long estimatedTimeMs = ((lengthInFrames - currentPosition) * 1000) /
+ audioTrack.getSampleRate();
+ if (DBG) Log.d(TAG, "About to sleep for : " + estimatedTimeMs + " ms," +
+ " Playback position : " + currentPosition);
+ try {
+ Thread.sleep(estimatedTimeMs);
+ } catch (InterruptedException ie) {
+ break;
+ }
+ }
+ }
+
+ private int getBytesPerFrame(int audioFormat) {
+ if (audioFormat == AudioFormat.ENCODING_PCM_8BIT) {
+ return 1;
+ } else if (audioFormat == AudioFormat.ENCODING_PCM_16BIT) {
+ return 2;
+ }
+
+ return -1;
+ }
+
+ private int getChannelConfig(int channelCount) {
if (channelCount == 1) {
- channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ return AudioFormat.CHANNEL_OUT_MONO;
} else if (channelCount == 2){
- channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
- } else {
- Log.e(TAG, "Unsupported number of channels: " + channelCount);
+ return AudioFormat.CHANNEL_OUT_STEREO;
+ }
+
+ return -1;
+ }
+
+ private AudioTrack createStreamingAudioTrack(int sampleRateInHz, int audioFormat,
+ int channelCount) {
+ int channelConfig = getChannelConfig(channelCount);
+
+ if (channelConfig < 0) {
+ Log.e(TAG, "Unsupported number of channels : " + channelCount);
return null;
}
@@ -244,10 +307,11 @@
= AudioTrack.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
int bufferSizeInBytes = Math.max(MIN_AUDIO_BUFFER_SIZE, minBufferSizeInBytes);
AudioTrack audioTrack = new AudioTrack(mStreamType, sampleRateInHz, channelConfig,
- audioFormat, bufferSizeInBytes, mode);
+ audioFormat, bufferSizeInBytes, AudioTrack.MODE_STREAM);
if (audioTrack == null) {
return null;
}
+
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
audioTrack.release();
return null;
@@ -255,4 +319,4 @@
setupVolume(audioTrack, mVolume, mPan);
return audioTrack;
}
-}
\ No newline at end of file
+}
diff --git a/core/java/android/speech/tts/SynthesisRequest.java b/core/java/android/speech/tts/SynthesisRequest.java
index f4bb852..515218b 100644
--- a/core/java/android/speech/tts/SynthesisRequest.java
+++ b/core/java/android/speech/tts/SynthesisRequest.java
@@ -114,6 +114,11 @@
public abstract int getMaxBufferSize();
/**
+ * Checks whether the synthesis request completed successfully.
+ */
+ abstract boolean isDone();
+
+ /**
* Aborts the speech request.
*
* Can be called from multiple threads.
@@ -162,6 +167,14 @@
public abstract int done();
/**
+ * The service should call this method if the speech synthesis fails.
+ *
+ * This method should only be called on the synthesis thread,
+ * while in {@link TextToSpeechService#onSynthesizeText}.
+ */
+ public abstract void error();
+
+ /**
* The service can call this method instead of using {@link #start}, {@link #audioAvailable}
* and {@link #done} if all the audio data is available in a single buffer.
*
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index a408ea2..da97fb4 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -150,12 +150,10 @@
*
* Called on the synthesis thread.
*
- * @param request The synthesis request. The method should
- * call {@link SynthesisRequest#start}, {@link SynthesisRequest#audioAvailable},
- * and {@link SynthesisRequest#done} on this request.
- * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
+ * @param request The synthesis request. The method should use the methods in the request
+ * object to communicate the results of the synthesis.
*/
- protected abstract int onSynthesizeText(SynthesisRequest request);
+ protected abstract void onSynthesizeText(SynthesisRequest request);
private boolean areDefaultsEnforced() {
return getSecureSettingInt(Settings.Secure.TTS_USE_DEFAULTS,
@@ -442,7 +440,8 @@
synthesisRequest = mSynthesisRequest;
}
setRequestParams(synthesisRequest);
- return TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+ TextToSpeechService.this.onSynthesizeText(synthesisRequest);
+ return synthesisRequest.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
}
protected SynthesisRequest createSynthesisRequest() {
diff --git a/core/java/android/util/JsonReader.java b/core/java/android/util/JsonReader.java
index 563c500..132b595 100644
--- a/core/java/android/util/JsonReader.java
+++ b/core/java/android/util/JsonReader.java
@@ -16,12 +16,13 @@
package android.util;
+import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.Reader;
-import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
+import libcore.internal.StringPool;
/**
* Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
@@ -177,6 +178,8 @@
private static final String TRUE = "true";
private static final String FALSE = "false";
+ private final StringPool stringPool = new StringPool();
+
/** The input JSON. */
private final Reader in;
@@ -836,7 +839,7 @@
if (skipping) {
return "skipped!";
} else if (builder == null) {
- return new String(buffer, start, pos - start - 1);
+ return stringPool.get(buffer, start, pos - start - 1);
} else {
builder.append(buffer, start, pos - start - 1);
return builder.toString();
@@ -934,7 +937,7 @@
} else if (skipping) {
result = "skipped!";
} else if (builder == null) {
- result = new String(buffer, pos, i);
+ result = stringPool.get(buffer, pos, i);
} else {
builder.append(buffer, pos, i);
result = builder.toString();
@@ -968,7 +971,7 @@
if (pos + 4 > limit && !fillBuffer(4)) {
throw syntaxError("Unterminated escape sequence");
}
- String hex = new String(buffer, pos, 4);
+ String hex = stringPool.get(buffer, pos, 4);
pos += 4;
return (char) Integer.parseInt(hex, 16);
@@ -1040,7 +1043,7 @@
value = FALSE;
return JsonToken.BOOLEAN;
} else {
- value = new String(buffer, valuePos, valueLength);
+ value = stringPool.get(buffer, valuePos, valueLength);
return decodeNumber(buffer, valuePos, valueLength);
}
}
diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java
index f284f51..1ccc66f 100644
--- a/core/java/android/view/GestureDetector.java
+++ b/core/java/android/view/GestureDetector.java
@@ -590,8 +590,14 @@
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
break;
+
case MotionEvent.ACTION_CANCEL:
cancel();
+ break;
+ }
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 0);
}
return handled;
}
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 6618f07..b5ca2c2 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -54,6 +54,7 @@
// Copy of the most recent events.
private InputEvent[] mRecentEvents;
+ private boolean[] mRecentEventsUnhandled;
private int mMostRecentEventIndex;
// Current event and its type.
@@ -65,6 +66,7 @@
// Current state of the trackball.
private boolean mTrackballDown;
+ private boolean mTrackballUnhandled;
// Bitfield of pointer ids that are currently down.
// Assumes that the largest possible pointer id is 31, which is potentially subject to change.
@@ -79,6 +81,9 @@
// Reset on down or cancel.
private boolean mTouchEventStreamIsTainted;
+ // Set to true if the touch event stream is partially unhandled.
+ private boolean mTouchEventStreamUnhandled;
+
// Set to true if we received hover enter.
private boolean mHoverEntered;
@@ -117,9 +122,17 @@
mLastEvent = null;
mLastNestingLevel = 0;
mTrackballDown = false;
+ mTrackballUnhandled = false;
mTouchEventStreamPointers = 0;
mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
mHoverEntered = false;
+
+ while (mKeyStateList != null) {
+ final KeyState state = mKeyStateList;
+ mKeyStateList = state.next;
+ state.recycle();
+ }
}
/**
@@ -176,7 +189,9 @@
// We don't perform this check when processing raw device input
// because the input dispatcher itself is responsible for setting
// the key repeat count before it delivers input events.
- if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
+ if (state.unhandled) {
+ state.unhandled = false;
+ } else if ((mFlags & FLAG_RAW_DEVICE_INPUT) == 0
&& event.getRepeatCount() == 0) {
problem("ACTION_DOWN but key is already down and this event "
+ "is not a key repeat.");
@@ -229,10 +244,11 @@
if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
switch (action) {
case MotionEvent.ACTION_DOWN:
- if (mTrackballDown) {
+ if (mTrackballDown && !mTrackballUnhandled) {
problem("ACTION_DOWN but trackball is already down.");
} else {
mTrackballDown = true;
+ mTrackballUnhandled = false;
}
ensureHistorySizeIsZeroForThisAction(event);
ensurePointerCountIsOneForThisAction(event);
@@ -242,6 +258,7 @@
problem("ACTION_UP but trackball is not down.");
} else {
mTrackballDown = false;
+ mTrackballUnhandled = false;
}
ensureHistorySizeIsZeroForThisAction(event);
ensurePointerCountIsOneForThisAction(event);
@@ -285,11 +302,13 @@
final int action = event.getAction();
final boolean newStream = action == MotionEvent.ACTION_DOWN
|| action == MotionEvent.ACTION_CANCEL;
- if (mTouchEventStreamIsTainted) {
+ if (mTouchEventStreamIsTainted || mTouchEventStreamUnhandled) {
if (newStream) {
mTouchEventStreamIsTainted = false;
+ mTouchEventStreamUnhandled = false;
+ mTouchEventStreamPointers = 0;
} else {
- finishEvent(true);
+ finishEvent(mTouchEventStreamIsTainted);
return;
}
}
@@ -467,6 +486,48 @@
}
}
+ /**
+ * Notifies the verifier that a given event was unhandled and the rest of the
+ * trace for the event should be ignored.
+ * This method should only be called if the event was previously checked by
+ * the consistency verifier using {@link #onInputEvent} and other methods.
+ * @param event The event.
+ * @param nestingLevel The nesting level: 0 if called from the base class,
+ * or 1 from a subclass. If the event was already checked by this consistency verifier
+ * at a higher nesting level, it will not be checked again. Used to handle the situation
+ * where a subclass dispatching method delegates to its superclass's dispatching method
+ * and both dispatching methods call into the consistency verifier.
+ */
+ public void onUnhandledEvent(InputEvent event, int nestingLevel) {
+ if (nestingLevel != mLastNestingLevel) {
+ return;
+ }
+
+ if (mRecentEventsUnhandled != null) {
+ mRecentEventsUnhandled[mMostRecentEventIndex] = true;
+ }
+
+ if (event instanceof KeyEvent) {
+ final KeyEvent keyEvent = (KeyEvent)event;
+ final int deviceId = keyEvent.getDeviceId();
+ final int source = keyEvent.getSource();
+ final int keyCode = keyEvent.getKeyCode();
+ final KeyState state = findKeyState(deviceId, source, keyCode, /*remove*/ false);
+ if (state != null) {
+ state.unhandled = true;
+ }
+ } else {
+ final MotionEvent motionEvent = (MotionEvent)event;
+ if (motionEvent.isTouchEvent()) {
+ mTouchEventStreamUnhandled = true;
+ } else if ((motionEvent.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (mTrackballDown) {
+ mTrackballUnhandled = true;
+ }
+ }
+ }
+ }
+
private void ensureMetaStateIsNormalized(int metaState) {
final int normalizedMetaState = KeyEvent.normalizeMetaState(metaState);
if (normalizedMetaState != metaState) {
@@ -518,7 +579,8 @@
private void finishEvent(boolean tainted) {
if (mViolationMessage != null && mViolationMessage.length() != 0) {
mViolationMessage.append("\n in ").append(mCaller);
- mViolationMessage.append("\n ").append(mCurrentEvent);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, 0, mCurrentEvent, false);
if (RECENT_EVENTS_TO_LOG != 0 && mRecentEvents != null) {
mViolationMessage.append("\n -- recent events --");
@@ -529,7 +591,8 @@
if (event == null) {
break;
}
- mViolationMessage.append("\n ").append(i + 1).append(": ").append(event);
+ mViolationMessage.append("\n ");
+ appendEvent(mViolationMessage, i + 1, event, mRecentEventsUnhandled[index]);
}
}
@@ -547,6 +610,7 @@
if (RECENT_EVENTS_TO_LOG != 0) {
if (mRecentEvents == null) {
mRecentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
+ mRecentEventsUnhandled = new boolean[RECENT_EVENTS_TO_LOG];
}
final int index = (mMostRecentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
mMostRecentEventIndex = index;
@@ -554,12 +618,23 @@
mRecentEvents[index].recycle();
}
mRecentEvents[index] = mCurrentEvent.copy();
+ mRecentEventsUnhandled[index] = false;
}
mCurrentEvent = null;
mCurrentEventType = null;
}
+ private static void appendEvent(StringBuilder message, int index,
+ InputEvent event, boolean unhandled) {
+ message.append(index).append(": sent at ").append(event.getEventTimeNano());
+ message.append(", ");
+ if (unhandled) {
+ message.append("(unhandled) ");
+ }
+ message.append(event);
+ }
+
private void problem(String message) {
if (mViolationMessage == null) {
mViolationMessage = new StringBuilder();
@@ -608,6 +683,7 @@
public int deviceId;
public int source;
public int keyCode;
+ public boolean unhandled;
private KeyState() {
}
@@ -625,6 +701,7 @@
state.deviceId = deviceId;
state.source = source;
state.keyCode = keyCode;
+ state.unhandled = false;
return state;
}
diff --git a/core/java/android/view/PointerIcon.aidl b/core/java/android/view/PointerIcon.aidl
new file mode 100644
index 0000000..b09340b
--- /dev/null
+++ b/core/java/android/view/PointerIcon.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.view;
+
+parcelable PointerIcon;
diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java
new file mode 100644
index 0000000..bb7ed41
--- /dev/null
+++ b/core/java/android/view/PointerIcon.java
@@ -0,0 +1,435 @@
+/*
+ * 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.view;
+
+import com.android.internal.util.XmlUtils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * Represents an icon that can be used as a mouse pointer.
+ * <p>
+ * Pointer icons can be provided either by the system using system styles,
+ * or by applications using bitmaps or application resources.
+ * </p>
+ *
+ * @hide
+ */
+public final class PointerIcon implements Parcelable {
+ private static final String TAG = "PointerIcon";
+
+ /** Style constant: Custom icon with a user-supplied bitmap. */
+ public static final int STYLE_CUSTOM = -1;
+
+ /** Style constant: Null icon. It has no bitmap. */
+ public static final int STYLE_NULL = 0;
+
+ /** Style constant: Arrow icon. (Default mouse pointer) */
+ public static final int STYLE_ARROW = 1000;
+
+ /** {@hide} Style constant: Spot hover icon for touchpads. */
+ public static final int STYLE_SPOT_HOVER = 2000;
+
+ /** {@hide} Style constant: Spot touch icon for touchpads. */
+ public static final int STYLE_SPOT_TOUCH = 2001;
+
+ /** {@hide} Style constant: Spot anchor icon for touchpads. */
+ public static final int STYLE_SPOT_ANCHOR = 2002;
+
+ // OEM private styles should be defined starting at this range to avoid
+ // conflicts with any system styles that may be defined in the future.
+ private static final int STYLE_OEM_FIRST = 10000;
+
+ // The default pointer icon.
+ private static final int STYLE_DEFAULT = STYLE_ARROW;
+
+ private static final PointerIcon gNullIcon = new PointerIcon(STYLE_NULL);
+
+ private final int mStyle;
+ private int mSystemIconResourceId;
+ private Bitmap mBitmap;
+ private float mHotSpotX;
+ private float mHotSpotY;
+
+ private PointerIcon(int style) {
+ mStyle = style;
+ }
+
+ /**
+ * Gets a special pointer icon that has no bitmap.
+ *
+ * @return The null pointer icon.
+ *
+ * @see #STYLE_NULL
+ */
+ public static PointerIcon getNullIcon() {
+ return gNullIcon;
+ }
+
+ /**
+ * Gets the default pointer icon.
+ *
+ * @param context The context.
+ * @return The default pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getDefaultIcon(Context context) {
+ return getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ /**
+ * Gets a system pointer icon for the given style.
+ * If style is not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param style The pointer icon style.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIcon getSystemIcon(Context context, int style) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (style == STYLE_NULL) {
+ return gNullIcon;
+ }
+
+ int styleIndex = getSystemIconStyleIndex(style);
+ if (styleIndex == 0) {
+ styleIndex = getSystemIconStyleIndex(STYLE_DEFAULT);
+ }
+
+ TypedArray a = context.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.Pointer,
+ com.android.internal.R.attr.pointerStyle, 0);
+ int resourceId = a.getResourceId(styleIndex, -1);
+ a.recycle();
+
+ if (resourceId == -1) {
+ Log.w(TAG, "Missing theme resources for pointer icon style " + style);
+ return style == STYLE_DEFAULT ? gNullIcon : getSystemIcon(context, STYLE_DEFAULT);
+ }
+
+ PointerIcon icon = new PointerIcon(style);
+ if ((resourceId & 0xff000000) == 0x01000000) {
+ icon.mSystemIconResourceId = resourceId;
+ } else {
+ icon.loadResource(context.getResources(), resourceId);
+ }
+ return icon;
+ }
+
+ /**
+ * Creates a custom pointer from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotspotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotspotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIcon createCustomIcon(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (bitmap == null) {
+ throw new IllegalArgumentException("bitmap must not be null");
+ }
+ validateHotSpot(bitmap, hotSpotX, hotSpotY);
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.mBitmap = bitmap;
+ icon.mHotSpotX = hotSpotX;
+ icon.mHotSpotY = hotSpotY;
+ return icon;
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" />
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIcon loadCustomIcon(Resources resources, int resourceId) {
+ if (resources == null) {
+ throw new IllegalArgumentException("resources must not be null");
+ }
+
+ PointerIcon icon = new PointerIcon(STYLE_CUSTOM);
+ icon.loadResource(resources, resourceId);
+ return icon;
+ }
+
+ /**
+ * Loads the bitmap and hotspot information for a pointer icon, if it is not already loaded.
+ * Returns a pointer icon (not necessarily the same instance) with the information filled in.
+ *
+ * @param context The context.
+ * @return The loaded pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ * @see #isLoaded()
+ * @hide
+ */
+ public PointerIcon load(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+
+ if (mSystemIconResourceId == 0 || mBitmap != null) {
+ return this;
+ }
+
+ PointerIcon result = new PointerIcon(mStyle);
+ result.mSystemIconResourceId = mSystemIconResourceId;
+ result.loadResource(context.getResources(), mSystemIconResourceId);
+ return result;
+ }
+
+ /**
+ * Returns true if the pointer icon style is {@link #STYLE_NULL}.
+ *
+ * @return True if the pointer icon style is {@link #STYLE_NULL}.
+ */
+ public boolean isNullIcon() {
+ return mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Returns true if the pointer icon has been loaded and its bitmap and hotspot
+ * information are available.
+ *
+ * @return True if the pointer icon is loaded.
+ * @see #load(Context)
+ */
+ public boolean isLoaded() {
+ return mBitmap != null || mStyle == STYLE_NULL;
+ }
+
+ /**
+ * Gets the style of the pointer icon.
+ *
+ * @return The pointer icon style.
+ */
+ public int getStyle() {
+ return mStyle;
+ }
+
+ /**
+ * Gets the bitmap of the pointer icon.
+ *
+ * @return The pointer icon bitmap, or null if the style is {@link #STYLE_NULL}.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public Bitmap getBitmap() {
+ throwIfIconIsNotLoaded();
+ return mBitmap;
+ }
+
+ /**
+ * Gets the X offset of the pointer icon hotspot.
+ *
+ * @return The hotspot X offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotX() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotX;
+ }
+
+ /**
+ * Gets the Y offset of the pointer icon hotspot.
+ *
+ * @return The hotspot Y offset.
+ *
+ * @throws IllegalStateException if the bitmap is not loaded.
+ * @see #isLoaded()
+ * @see #load(Context)
+ */
+ public float getHotSpotY() {
+ throwIfIconIsNotLoaded();
+ return mHotSpotY;
+ }
+
+ private void throwIfIconIsNotLoaded() {
+ if (!isLoaded()) {
+ throw new IllegalStateException("The icon is not loaded.");
+ }
+ }
+
+ public static final Parcelable.Creator<PointerIcon> CREATOR
+ = new Parcelable.Creator<PointerIcon>() {
+ public PointerIcon createFromParcel(Parcel in) {
+ int style = in.readInt();
+ if (style == STYLE_NULL) {
+ return getNullIcon();
+ }
+
+ int systemIconResourceId = in.readInt();
+ if (systemIconResourceId != 0) {
+ PointerIcon icon = new PointerIcon(style);
+ icon.mSystemIconResourceId = systemIconResourceId;
+ return icon;
+ }
+
+ Bitmap bitmap = Bitmap.CREATOR.createFromParcel(in);
+ float hotSpotX = in.readFloat();
+ float hotSpotY = in.readFloat();
+ return PointerIcon.createCustomIcon(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public PointerIcon[] newArray(int size) {
+ return new PointerIcon[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mStyle);
+
+ if (mStyle != STYLE_NULL) {
+ out.writeInt(mSystemIconResourceId);
+ if (mSystemIconResourceId == 0) {
+ mBitmap.writeToParcel(out, flags);
+ out.writeFloat(mHotSpotX);
+ out.writeFloat(mHotSpotY);
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (other == null || !(other instanceof PointerIcon)) {
+ return false;
+ }
+
+ PointerIcon otherIcon = (PointerIcon) other;
+ if (mStyle != otherIcon.mStyle
+ || mSystemIconResourceId != otherIcon.mSystemIconResourceId) {
+ return false;
+ }
+
+ if (mSystemIconResourceId == 0 && (mBitmap != otherIcon.mBitmap
+ || mHotSpotX != otherIcon.mHotSpotX
+ || mHotSpotY != otherIcon.mHotSpotY)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private void loadResource(Resources resources, int resourceId) {
+ XmlResourceParser parser = resources.getXml(resourceId);
+ final int bitmapRes;
+ final float hotSpotX;
+ final float hotSpotY;
+ try {
+ XmlUtils.beginDocument(parser, "pointer-icon");
+
+ TypedArray a = resources.obtainAttributes(
+ parser, com.android.internal.R.styleable.PointerIcon);
+ bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
+ hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
+ hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
+ a.recycle();
+ } catch (Exception ex) {
+ throw new IllegalArgumentException("Exception parsing pointer icon resource.", ex);
+ } finally {
+ parser.close();
+ }
+
+ if (bitmapRes == 0) {
+ throw new IllegalArgumentException("<pointer-icon> is missing bitmap attribute.");
+ }
+
+ Drawable drawable = resources.getDrawable(bitmapRes);
+ if (!(drawable instanceof BitmapDrawable)) {
+ throw new IllegalArgumentException("<pointer-icon> bitmap attribute must "
+ + "refer to a bitmap drawable.");
+ }
+
+ // Set the properties now that we have successfully loaded the icon.
+ mBitmap = ((BitmapDrawable)drawable).getBitmap();
+ mHotSpotX = hotSpotX;
+ mHotSpotY = hotSpotY;
+ }
+
+ private static void validateHotSpot(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ if (hotSpotX < 0 || hotSpotX >= bitmap.getWidth()) {
+ throw new IllegalArgumentException("x hotspot lies outside of the bitmap area");
+ }
+ if (hotSpotY < 0 || hotSpotY >= bitmap.getHeight()) {
+ throw new IllegalArgumentException("y hotspot lies outside of the bitmap area");
+ }
+ }
+
+ private static int getSystemIconStyleIndex(int style) {
+ switch (style) {
+ case STYLE_ARROW:
+ return com.android.internal.R.styleable.Pointer_pointerIconArrow;
+ case STYLE_SPOT_HOVER:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotHover;
+ case STYLE_SPOT_TOUCH:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotTouch;
+ case STYLE_SPOT_ANCHOR:
+ return com.android.internal.R.styleable.Pointer_pointerIconSpotAnchor;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 456857a..5e07e1a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -183,15 +183,15 @@
}
final int action = event.getActionMasked();
- boolean handled = true;
if (action == MotionEvent.ACTION_DOWN) {
reset(); // Start fresh
}
- if (mInvalidGesture) return false;
-
- if (!mGestureInProgress) {
+ boolean handled = true;
+ if (mInvalidGesture) {
+ handled = false;
+ } else if (!mGestureInProgress) {
switch (action) {
case MotionEvent.ACTION_DOWN: {
mActiveId0 = event.getPointerId(0);
@@ -467,6 +467,10 @@
break;
}
}
+
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
return handled;
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index dc8e52f..fe8af19 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -4614,8 +4614,15 @@
return true;
}
- return event.dispatch(this, mAttachInfo != null
- ? mAttachInfo.mKeyDispatchState : null, this);
+ if (event.dispatch(this, mAttachInfo != null
+ ? mAttachInfo.mKeyDispatchState : null, this)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4640,16 +4647,22 @@
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
+ if (onFilterTouchEventForSecurity(event)) {
+ //noinspection SimplifiableIfStatement
+ if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
+ mOnTouchListener.onTouch(this, event)) {
+ return true;
+ }
+
+ if (onTouchEvent(event)) {
+ return true;
+ }
}
- //noinspection SimplifiableIfStatement
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
- return onTouchEvent(event);
+ return false;
}
/**
@@ -4682,7 +4695,14 @@
}
//Log.i("view", "view=" + this + ", " + event.toString());
- return onTrackballEvent(event);
+ if (onTrackballEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
@@ -4723,7 +4743,15 @@
&& mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
- return onGenericMotionEvent(event);
+
+ if (onGenericMotionEvent(event)) {
+ return true;
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
+ }
+ return false;
}
/**
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0d4f3d0..08daa28 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -1131,9 +1131,17 @@
}
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchKeyEvent(event);
+ if (super.dispatchKeyEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchKeyEvent(event);
+ if (mFocused.dispatchKeyEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1161,9 +1169,17 @@
}
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
- return super.dispatchTrackballEvent(event);
+ if (super.dispatchTrackballEvent(event)) {
+ return true;
+ }
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
- return mFocused.dispatchTrackballEvent(event);
+ if (mFocused.dispatchTrackballEvent(event)) {
+ return true;
+ }
+ }
+
+ if (mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
@@ -1344,155 +1360,158 @@
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
+ boolean handled = false;
+ if (onFilterTouchEventForSecurity(ev)) {
+ final int action = ev.getAction();
+ final int actionMasked = action & MotionEvent.ACTION_MASK;
- final int action = ev.getAction();
- final int actionMasked = action & MotionEvent.ACTION_MASK;
-
- // Handle an initial down.
- if (actionMasked == MotionEvent.ACTION_DOWN) {
- // Throw away all previous state when starting a new touch gesture.
- // The framework may have dropped the up or cancel event for the previous gesture
- // due to an app switch, ANR, or some other state change.
- cancelAndClearTouchTargets(ev);
- resetTouchState();
- }
-
- // Check for interception.
- final boolean intercepted;
- if (actionMasked == MotionEvent.ACTION_DOWN
- || mFirstTouchTarget != null) {
- final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
- if (!disallowIntercept) {
- intercepted = onInterceptTouchEvent(ev);
- ev.setAction(action); // restore action in case onInterceptTouchEvent() changed it
- } else {
- intercepted = false;
+ // Handle an initial down.
+ if (actionMasked == MotionEvent.ACTION_DOWN) {
+ // Throw away all previous state when starting a new touch gesture.
+ // The framework may have dropped the up or cancel event for the previous gesture
+ // due to an app switch, ANR, or some other state change.
+ cancelAndClearTouchTargets(ev);
+ resetTouchState();
}
- } else {
- // There are no touch targets and this action is not an initial down
- // so this view group continues to intercept touches.
- intercepted = true;
- }
- // Check for cancelation.
- final boolean canceled = resetCancelNextUpFlag(this)
- || actionMasked == MotionEvent.ACTION_CANCEL;
-
- // Update list of touch targets for pointer down, if needed.
- final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
- TouchTarget newTouchTarget = null;
- boolean alreadyDispatchedToNewTouchTarget = false;
- if (!canceled && !intercepted) {
+ // Check for interception.
+ final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
- || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- final int actionIndex = ev.getActionIndex(); // always 0 for down
- final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
- : TouchTarget.ALL_POINTER_IDS;
+ || mFirstTouchTarget != null) {
+ final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
+ if (!disallowIntercept) {
+ intercepted = onInterceptTouchEvent(ev);
+ ev.setAction(action); // restore action in case it was changed
+ } else {
+ intercepted = false;
+ }
+ } else {
+ // There are no touch targets and this action is not an initial down
+ // so this view group continues to intercept touches.
+ intercepted = true;
+ }
- // Clean up earlier touch targets for this pointer id in case they
- // have become out of sync.
- removePointersFromTouchTargets(idBitsToAssign);
+ // Check for cancelation.
+ final boolean canceled = resetCancelNextUpFlag(this)
+ || actionMasked == MotionEvent.ACTION_CANCEL;
- final int childrenCount = mChildrenCount;
- if (childrenCount != 0) {
- // Find a child that can receive the event. Scan children from front to back.
- final View[] children = mChildren;
- final float x = ev.getX(actionIndex);
- final float y = ev.getY(actionIndex);
+ // Update list of touch targets for pointer down, if needed.
+ final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
+ TouchTarget newTouchTarget = null;
+ boolean alreadyDispatchedToNewTouchTarget = false;
+ if (!canceled && !intercepted) {
+ if (actionMasked == MotionEvent.ACTION_DOWN
+ || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ final int actionIndex = ev.getActionIndex(); // always 0 for down
+ final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
+ : TouchTarget.ALL_POINTER_IDS;
- for (int i = childrenCount - 1; i >= 0; i--) {
- final View child = children[i];
- if (!canViewReceivePointerEvents(child)
- || !isTransformedTouchPointInView(x, y, child, null)) {
+ // Clean up earlier touch targets for this pointer id in case they
+ // have become out of sync.
+ removePointersFromTouchTargets(idBitsToAssign);
+
+ final int childrenCount = mChildrenCount;
+ if (childrenCount != 0) {
+ // Find a child that can receive the event.
+ // Scan children from front to back.
+ final View[] children = mChildren;
+ final float x = ev.getX(actionIndex);
+ final float y = ev.getY(actionIndex);
+
+ for (int i = childrenCount - 1; i >= 0; i--) {
+ final View child = children[i];
+ if (!canViewReceivePointerEvents(child)
+ || !isTransformedTouchPointInView(x, y, child, null)) {
+ continue;
+ }
+
+ newTouchTarget = getTouchTarget(child);
+ if (newTouchTarget != null) {
+ // Child is already receiving touch within its bounds.
+ // Give it the new pointer in addition to the ones it is handling.
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ break;
+ }
+
+ resetCancelNextUpFlag(child);
+ if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
+ // Child wants to receive touch within its bounds.
+ mLastTouchDownTime = ev.getDownTime();
+ mLastTouchDownIndex = i;
+ mLastTouchDownX = ev.getX();
+ mLastTouchDownY = ev.getY();
+ newTouchTarget = addTouchTarget(child, idBitsToAssign);
+ alreadyDispatchedToNewTouchTarget = true;
+ break;
+ }
+ }
+ }
+
+ if (newTouchTarget == null && mFirstTouchTarget != null) {
+ // Did not find a child to receive the event.
+ // Assign the pointer to the least recently added target.
+ newTouchTarget = mFirstTouchTarget;
+ while (newTouchTarget.next != null) {
+ newTouchTarget = newTouchTarget.next;
+ }
+ newTouchTarget.pointerIdBits |= idBitsToAssign;
+ }
+ }
+ }
+
+ // Dispatch to touch targets.
+ if (mFirstTouchTarget == null) {
+ // No touch targets so treat this as an ordinary view.
+ handled = dispatchTransformedTouchEvent(ev, canceled, null,
+ TouchTarget.ALL_POINTER_IDS);
+ } else {
+ // Dispatch to touch targets, excluding the new touch target if we already
+ // dispatched to it. Cancel touch targets if necessary.
+ TouchTarget predecessor = null;
+ TouchTarget target = mFirstTouchTarget;
+ while (target != null) {
+ final TouchTarget next = target.next;
+ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
+ handled = true;
+ } else {
+ final boolean cancelChild = resetCancelNextUpFlag(target.child)
+ || intercepted;
+ if (dispatchTransformedTouchEvent(ev, cancelChild,
+ target.child, target.pointerIdBits)) {
+ handled = true;
+ }
+ if (cancelChild) {
+ if (predecessor == null) {
+ mFirstTouchTarget = next;
+ } else {
+ predecessor.next = next;
+ }
+ target.recycle();
+ target = next;
continue;
}
-
- newTouchTarget = getTouchTarget(child);
- if (newTouchTarget != null) {
- // Child is already receiving touch within its bounds.
- // Give it the new pointer in addition to the ones it is handling.
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- break;
- }
-
- resetCancelNextUpFlag(child);
- if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
- // Child wants to receive touch within its bounds.
- mLastTouchDownTime = ev.getDownTime();
- mLastTouchDownIndex = i;
- mLastTouchDownX = ev.getX();
- mLastTouchDownY = ev.getY();
- newTouchTarget = addTouchTarget(child, idBitsToAssign);
- alreadyDispatchedToNewTouchTarget = true;
- break;
- }
}
+ predecessor = target;
+ target = next;
}
+ }
- if (newTouchTarget == null && mFirstTouchTarget != null) {
- // Did not find a child to receive the event.
- // Assign the pointer to the least recently added target.
- newTouchTarget = mFirstTouchTarget;
- while (newTouchTarget.next != null) {
- newTouchTarget = newTouchTarget.next;
- }
- newTouchTarget.pointerIdBits |= idBitsToAssign;
- }
+ // Update list of touch targets for pointer up or cancel, if needed.
+ if (canceled
+ || actionMasked == MotionEvent.ACTION_UP
+ || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
+ resetTouchState();
+ } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
+ final int actionIndex = ev.getActionIndex();
+ final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
+ removePointersFromTouchTargets(idBitsToRemove);
}
}
- // Dispatch to touch targets.
- boolean handled = false;
- if (mFirstTouchTarget == null) {
- // No touch targets so treat this as an ordinary view.
- handled = dispatchTransformedTouchEvent(ev, canceled, null,
- TouchTarget.ALL_POINTER_IDS);
- } else {
- // Dispatch to touch targets, excluding the new touch target if we already
- // dispatched to it. Cancel touch targets if necessary.
- TouchTarget predecessor = null;
- TouchTarget target = mFirstTouchTarget;
- while (target != null) {
- final TouchTarget next = target.next;
- if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
- handled = true;
- } else {
- final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
- if (dispatchTransformedTouchEvent(ev, cancelChild,
- target.child, target.pointerIdBits)) {
- handled = true;
- }
- if (cancelChild) {
- if (predecessor == null) {
- mFirstTouchTarget = next;
- } else {
- predecessor.next = next;
- }
- target.recycle();
- target = next;
- continue;
- }
- }
- predecessor = target;
- target = next;
- }
+ if (!handled && mInputEventConsistencyVerifier != null) {
+ mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
-
- // Update list of touch targets for pointer up or cancel, if needed.
- if (canceled
- || actionMasked == MotionEvent.ACTION_UP
- || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
- resetTouchState();
- } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
- final int actionIndex = ev.getActionIndex();
- final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
- removePointersFromTouchTargets(idBitsToRemove);
- }
-
return handled;
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index ea9e402..a6639d1 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -26,8 +26,9 @@
* is used to perform such things as reading text around the cursor,
* committing text to the text box, and sending raw key events to the application.
*
- * <p>Implementations of this interface should generally be done by
- * subclassing {@link BaseInputConnection}.
+ * <p>Applications should never directly implement this interface, but instead
+ * subclass from {@link BaseInputConnection}. This will ensure that the
+ * application does not break when new methods are added to the interface.
*/
public interface InputConnection {
/**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index e07e26e..b5d0492 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -871,8 +871,9 @@
*/
public static final int UNKNOWN_TYPE = 0;
/**
- * HitTestResult for hitting a HTML::a tag
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int ANCHOR_TYPE = 1;
/**
* HitTestResult for hitting a phone number
@@ -891,8 +892,9 @@
*/
public static final int IMAGE_TYPE = 5;
/**
- * HitTestResult for hitting a HTML::a tag which contains HTML::img
+ * @deprecated This type is no longer used.
*/
+ @Deprecated
public static final int IMAGE_ANCHOR_TYPE = 6;
/**
* HitTestResult for hitting a HTML::a tag with src=http
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index fd0e53d..dbe9288 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -230,6 +230,30 @@
requestLayout();
}
+ /**
+ * Set padding displayed on both ends of dividers.
+ *
+ * @param padding Padding value in pixels that will be applied to each end
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #getDividerPadding()
+ */
+ public void setDividerPadding(int padding) {
+ mDividerPadding = padding;
+ }
+
+ /**
+ * Get the padding size used to inset dividers in pixels
+ *
+ * @see #setShowDividers(int)
+ * @see #setDividerDrawable(Drawable)
+ * @see #setDividerPadding(int)
+ */
+ public int getDividerPadding() {
+ return mDividerPadding;
+ }
+
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
@@ -244,29 +268,15 @@
}
void drawDividersVertical(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int top = getPaddingTop();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
top += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawHorizontalDivider(canvas, top);
- top += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawHorizontalDivider(canvas, top);
top += mDividerHeight;
}
@@ -276,35 +286,21 @@
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawHorizontalDivider(canvas, top);
}
}
void drawDividersHorizontal(Canvas canvas) {
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
- final boolean showDividerEnd =
- (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END;
-
final int count = getVirtualChildCount();
int left = getPaddingLeft();
- boolean firstVisible = true;
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
left += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- drawVerticalDivider(canvas, left);
- left += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
drawVerticalDivider(canvas, left);
left += mDividerWidth;
}
@@ -314,7 +310,7 @@
}
}
- if (showDividerEnd) {
+ if (hasDividerBeforeChildAt(count)) {
drawVerticalDivider(canvas, left);
}
}
@@ -523,6 +519,23 @@
}
/**
+ * Determines where to position dividers between children.
+ *
+ * @param childIndex Index of child to check for preceding divider
+ * @return true if there should be a divider before the child at childIndex
+ * @hide Pending API consideration. Currently only used internally by the system.
+ */
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ if (childIndex == 0) {
+ return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
+ } else if (childIndex == getChildCount()) {
+ return (mShowDividers & SHOW_DIVIDER_END) != 0;
+ } else {
+ return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
+ }
+ }
+
+ /**
* Measures the children when the orientation of this LinearLayout is set
* to {@link #VERTICAL}.
*
@@ -554,14 +567,7 @@
int largestChildHeight = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how tall everyone is. Also remember max width.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -575,12 +581,7 @@
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerHeight;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
@@ -677,7 +678,7 @@
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerHeight;
}
@@ -881,14 +882,7 @@
int largestChildWidth = Integer.MIN_VALUE;
- // A divider at the end will change how much space views can consume.
- final boolean showDividerBeginning =
- (mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING;
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
// See how wide everyone is. Also remember max height.
- boolean firstVisible = true;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
@@ -902,12 +896,7 @@
continue;
}
- if (firstVisible) {
- firstVisible = false;
- if (showDividerBeginning) {
- mTotalLength += mDividerWidth;
- }
- } else if (showDividerMiddle) {
+ if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerWidth;
}
@@ -1022,7 +1011,7 @@
i += getChildrenSkipCount(child, i);
}
- if (mTotalLength > 0 && (mShowDividers & SHOW_DIVIDER_END) == SHOW_DIVIDER_END) {
+ if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {
mTotalLength += mDividerWidth;
}
@@ -1358,13 +1347,6 @@
}
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childTop += mDividerHeight;
- }
-
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
@@ -1399,15 +1381,15 @@
break;
}
+ if (hasDividerBeforeChildAt(i)) {
+ childTop += mDividerHeight;
+ }
+
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
- if (showDividerMiddle) {
- childTop += mDividerHeight;
- }
-
i += getChildrenSkipCount(child, i);
}
}
@@ -1458,13 +1440,6 @@
}
}
- final boolean showDividerMiddle =
- (mShowDividers & SHOW_DIVIDER_MIDDLE) == SHOW_DIVIDER_MIDDLE;
-
- if ((mShowDividers & SHOW_DIVIDER_BEGINNING) == SHOW_DIVIDER_BEGINNING) {
- childLeft += mDividerWidth;
- }
-
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
@@ -1523,16 +1498,16 @@
break;
}
+ if (hasDividerBeforeChildAt(i)) {
+ childLeft += mDividerWidth;
+ }
+
childLeft += lp.leftMargin;
setChildFrame(child, childLeft + getLocationOffset(child), childTop,
childWidth, childHeight);
childLeft += childWidth + lp.rightMargin +
getNextLocationOffset(child);
- if (showDividerMiddle) {
- childLeft += mDividerWidth;
- }
-
i += getChildrenSkipCount(child, i);
}
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index af954c9..d76a956 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -251,7 +251,7 @@
*/
public void addHeaderView(View v, Object data, boolean isSelectable) {
- if (mAdapter != null) {
+ if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) {
throw new IllegalStateException(
"Cannot add header view to list -- setAdapter has already been called.");
}
@@ -261,6 +261,12 @@
info.data = data;
info.isSelectable = isSelectable;
mHeaderViewInfos.add(info);
+
+ // in the case of re-adding a header view, or adding one later on,
+ // we need to notify the observer
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
}
/**
@@ -294,7 +300,9 @@
if (mHeaderViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeHeader(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mHeaderViewInfos);
@@ -328,6 +336,12 @@
* @param isSelectable true if the footer view can be selected
*/
public void addFooterView(View v, Object data, boolean isSelectable) {
+
+ // NOTE: do not enforce the adapter being null here, since unlike in
+ // addHeaderView, it was never enforced here, and so existing apps are
+ // relying on being able to add a footer and then calling setAdapter to
+ // force creation of the HeaderViewListAdapter wrapper
+
FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
@@ -371,7 +385,9 @@
if (mFooterViewInfos.size() > 0) {
boolean result = false;
if (((HeaderViewListAdapter) mAdapter).removeFooter(v)) {
- mDataSetObserver.onChanged();
+ if (mDataSetObserver != null) {
+ mDataSetObserver.onChanged();
+ }
result = true;
}
removeFixedViewInfo(v, mFooterViewInfos);
diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java
index 11b594c..16d5539 100644
--- a/core/java/com/android/internal/app/ActionBarImpl.java
+++ b/core/java/com/android/internal/app/ActionBarImpl.java
@@ -687,7 +687,7 @@
return;
}
invalidate();
- mUpperContextView.openOverflowMenu();
+ mUpperContextView.showOverflowMenu();
}
}
diff --git a/core/java/com/android/internal/util/HierarchicalState.java b/core/java/com/android/internal/util/IState.java
similarity index 61%
rename from core/java/com/android/internal/util/HierarchicalState.java
rename to core/java/com/android/internal/util/IState.java
index b37f46c..056f8e9 100644
--- a/core/java/com/android/internal/util/HierarchicalState.java
+++ b/core/java/com/android/internal/util/IState.java
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2009 The Android Open Source Project
+ * 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.
@@ -21,21 +21,29 @@
/**
* {@hide}
*
- * The class for implementing states in a HierarchicalStateMachine
+ * The interface for implementing states in a {@link StateMachine}
*/
-public class HierarchicalState {
+public interface IState {
/**
- * Constructor
+ * Returned by processMessage to indicate the the message was processed.
*/
- protected HierarchicalState() {
- }
+ static final boolean HANDLED = true;
+
+ /**
+ * Returned by processMessage to indicate the the message was NOT processed.
+ */
+ static final boolean NOT_HANDLED = false;
/**
* Called when a state is entered.
*/
- protected void enter() {
- }
+ void enter();
+
+ /**
+ * Called when a state is exited.
+ */
+ void exit();
/**
* Called when a message is to be processed by the
@@ -49,28 +57,15 @@
* be processed until this routine returns.
*
* @param msg to process
- * @return true if processing has completed and false
- * if the parent state's processMessage should
- * be invoked.
+ * @return HANDLED if processing has completed and NOT_HANDLED
+ * if the message wasn't processed.
*/
- protected boolean processMessage(Message msg) {
- return false;
- }
+ boolean processMessage(Message msg);
/**
- * Called when a state is exited.
+ * Name of State for debugging purposes.
+ *
+ * @return name of state.
*/
- protected void exit() {
- }
-
- /**
- * @return name of state, but default returns the states
- * class name. An instance name would be better but requiring
- * it seems unnecessary.
- */
- public String getName() {
- String name = getClass().getName();
- int lastDollar = name.lastIndexOf('$');
- return name.substring(lastDollar + 1);
- }
+ String getName();
}
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
new file mode 100644
index 0000000..3eadff5
--- /dev/null
+++ b/core/java/com/android/internal/util/State.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.Message;
+
+/**
+ * {@hide}
+ *
+ * The class for implementing states in a StateMachine
+ */
+public class State implements IState {
+
+ /**
+ * Constructor
+ */
+ protected State() {
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.internal.util.IState#enter()
+ */
+ @Override
+ public void enter() {
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.internal.util.IState#exit()
+ */
+ @Override
+ public void exit() {
+ }
+
+ /* (non-Javadoc)
+ * @see com.android.internal.util.IState#processMessage(android.os.Message)
+ */
+ @Override
+ public boolean processMessage(Message msg) {
+ return false;
+ }
+
+ /**
+ * Name of State for debugging purposes.
+ *
+ * This default implementation returns the class name, returning
+ * the instance name would better in cases where a State class
+ * is used for multiple states. But normally there is one class per
+ * state and the class name is sufficient and easy to get. You may
+ * want to provide a setName or some other mechanism for setting
+ * another name if the class name is not appropriate.
+ *
+ * @see com.android.internal.util.IState#processMessage(android.os.Message)
+ */
+ @Override
+ public String getName() {
+ String name = getClass().getName();
+ int lastDollar = name.lastIndexOf('$');
+ return name.substring(lastDollar + 1);
+ }
+}
diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/StateMachine.java
similarity index 85%
rename from core/java/com/android/internal/util/HierarchicalStateMachine.java
rename to core/java/com/android/internal/util/StateMachine.java
index f6aa184..cbe72dd 100644
--- a/core/java/com/android/internal/util/HierarchicalStateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -29,10 +29,10 @@
/**
* {@hide}
*
- * <p>A hierarchical state machine is a state machine which processes messages
+ * <p>The state machine defined here is a hierarchical state machine which processes messages
* and can have states arranged hierarchically.</p>
*
- * <p>A state is a <code>HierarchicalState</code> object and must implement
+ * <p>A state is a <code>State</code> object and must implement
* <code>processMessage</code> and optionally <code>enter/exit/getName</code>.
* The enter/exit methods are equivalent to the construction and destruction
* in Object Oriented programming and are used to perform initialization and
@@ -76,7 +76,7 @@
* will exit the current state and its parent and then exit from the controlling thread
* and no further messages will be processed.</p>
*
- * <p>In addition to <code>processMessage</code> each <code>HierarchicalState</code> has
+ * <p>In addition to <code>processMessage</code> each <code>State</code> has
* an <code>enter</code> method and <code>exit</exit> method which may be overridden.</p>
*
* <p>Since the states are arranged in a hierarchy transitioning to a new state
@@ -122,11 +122,11 @@
* mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
* when the next message is received mS4.processMessage will be invoked.</p>
*
- * <p>Now for some concrete examples, here is the canonical HelloWorld as an HSM.
+ * <p>Now for some concrete examples, here is the canonical HelloWorld as a state machine.
* It responds with "Hello World" being printed to the log for every message.</p>
<code>
-class HelloWorld extends HierarchicalStateMachine {
- Hsm1(String name) {
+class HelloWorld extends StateMachine {
+ HelloWorld(String name) {
super(name);
addState(mState1);
setInitialState(mState1);
@@ -138,7 +138,7 @@
return hw;
}
- class State1 extends HierarchicalState {
+ class State1 extends State {
@Override public boolean processMessage(Message message) {
Log.d(TAG, "Hello World");
return HANDLED;
@@ -221,9 +221,9 @@
}
}
</code>
- * <p>The implementation is below and also in HierarchicalStateMachineTest:</p>
+ * <p>The implementation is below and also in StateMachineTest:</p>
<code>
-class Hsm1 extends HierarchicalStateMachine {
+class Hsm1 extends StateMachine {
private static final String TAG = "hsm1";
public static final int CMD_1 = 1;
@@ -255,7 +255,7 @@
Log.d(TAG, "ctor X");
}
- class P1 extends HierarchicalState {
+ class P1 extends State {
@Override public void enter() {
Log.d(TAG, "mP1.enter");
}
@@ -282,7 +282,7 @@
}
}
- class S1 extends HierarchicalState {
+ class S1 extends State {
@Override public void enter() {
Log.d(TAG, "mS1.enter");
}
@@ -302,7 +302,7 @@
}
}
- class S2 extends HierarchicalState {
+ class S2 extends State {
@Override public void enter() {
Log.d(TAG, "mS2.enter");
}
@@ -330,7 +330,7 @@
}
}
- class P2 extends HierarchicalState {
+ class P2 extends State {
@Override public void enter() {
Log.d(TAG, "mP2.enter");
sendMessage(obtainMessage(CMD_5));
@@ -409,16 +409,16 @@
D/hsm1 ( 1999): halting
</code>
*/
-public class HierarchicalStateMachine {
+public class StateMachine {
- private static final String TAG = "HierarchicalStateMachine";
+ private static final String TAG = "StateMachine";
private String mName;
/** Message.what value when quitting */
- public static final int HSM_QUIT_CMD = -1;
+ public static final int SM_QUIT_CMD = -1;
/** Message.what value when initializing */
- public static final int HSM_INIT_CMD = -1;
+ public static final int SM_INIT_CMD = -1;
/**
* Convenience constant that maybe returned by processMessage
@@ -441,8 +441,8 @@
*/
public static class ProcessedMessageInfo {
private int what;
- private HierarchicalState state;
- private HierarchicalState orgState;
+ private State state;
+ private State orgState;
/**
* Constructor
@@ -451,7 +451,7 @@
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- ProcessedMessageInfo(Message message, HierarchicalState state, HierarchicalState orgState) {
+ ProcessedMessageInfo(Message message, State state, State orgState) {
update(message, state, orgState);
}
@@ -461,7 +461,7 @@
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- public void update(Message message, HierarchicalState state, HierarchicalState orgState) {
+ public void update(Message message, State state, State orgState) {
this.what = message.what;
this.state = state;
this.orgState = orgState;
@@ -477,14 +477,14 @@
/**
* @return the state that handled this message
*/
- public HierarchicalState getState() {
+ public State getState() {
return state;
}
/**
* @return the original state that received the message.
*/
- public HierarchicalState getOriginalState() {
+ public State getOriginalState() {
return orgState;
}
@@ -593,7 +593,7 @@
* @param orgState is the first state the received the message but
* did not processes the message.
*/
- void add(Message message, HierarchicalState state, HierarchicalState orgState) {
+ void add(Message message, State state, State orgState) {
mCount += 1;
if (mMessages.size() < mMaxSize) {
mMessages.add(new ProcessedMessageInfo(message, state, orgState));
@@ -608,7 +608,7 @@
}
}
- private static class HsmHandler extends Handler {
+ private static class SmHandler extends Handler {
/** The debug flag */
private boolean mDbg = false;
@@ -643,8 +643,8 @@
/** State used when state machine is quitting */
private QuittingState mQuittingState = new QuittingState();
- /** Reference to the HierarchicalStateMachine */
- private HierarchicalStateMachine mHsm;
+ /** Reference to the StateMachine */
+ private StateMachine mSm;
/**
* Information about a state.
@@ -652,7 +652,7 @@
*/
private class StateInfo {
/** The state */
- HierarchicalState state;
+ State state;
/** The parent of this state, null if there is no parent */
StateInfo parentStateInfo;
@@ -672,14 +672,14 @@
}
/** The map of all of the states in the state machine */
- private HashMap<HierarchicalState, StateInfo> mStateInfo =
- new HashMap<HierarchicalState, StateInfo>();
+ private HashMap<State, StateInfo> mStateInfo =
+ new HashMap<State, StateInfo>();
/** The initial state that will process the first message */
- private HierarchicalState mInitialState;
+ private State mInitialState;
/** The destination state when transitionTo has been invoked */
- private HierarchicalState mDestState;
+ private State mDestState;
/** The list of deferred messages */
private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
@@ -687,10 +687,10 @@
/**
* State entered when transitionToHaltingState is called.
*/
- private class HaltingState extends HierarchicalState {
+ private class HaltingState extends State {
@Override
public boolean processMessage(Message msg) {
- mHsm.haltedProcessMessage(msg);
+ mSm.haltedProcessMessage(msg);
return true;
}
}
@@ -698,7 +698,7 @@
/**
* State entered when a valid quit message is handled.
*/
- private class QuittingState extends HierarchicalState {
+ private class QuittingState extends State {
@Override
public boolean processMessage(Message msg) {
return NOT_HANDLED;
@@ -745,7 +745,7 @@
* the appropriate states. We loop on this to allow
* enter and exit methods to use transitionTo.
*/
- HierarchicalState destState = null;
+ State destState = null;
while (mDestState != null) {
if (mDbg) Log.d(TAG, "handleMessage: new destination call exit");
@@ -785,11 +785,11 @@
/**
* We are quitting so ignore all messages.
*/
- mHsm.quitting();
- if (mHsm.mHsmThread != null) {
+ mSm.quitting();
+ if (mSm.mSmThread != null) {
// If we made the thread then quit looper which stops the thread.
getLooper().quit();
- mHsm.mHsmThread = null;
+ mSm.mSmThread = null;
}
} else if (destState == mHaltingState) {
/**
@@ -797,7 +797,7 @@
* state. All subsequent messages will be processed in
* in the halting state which invokes haltedProcessMessage(msg);
*/
- mHsm.halting();
+ mSm.halting();
}
}
}
@@ -833,7 +833,7 @@
* starting at the first entry.
*/
mIsConstructionCompleted = true;
- mMsg = obtainMessage(HSM_INIT_CMD);
+ mMsg = obtainMessage(SM_INIT_CMD);
invokeEnterMethods(0);
/**
@@ -863,7 +863,7 @@
/**
* No parents left so it's not handled
*/
- mHsm.unhandledMessage(msg);
+ mSm.unhandledMessage(msg);
if (isQuit(msg)) {
transitionTo(mQuittingState);
}
@@ -878,7 +878,7 @@
* Record that we processed the message
*/
if (curStateInfo != null) {
- HierarchicalState orgState = mStateStack[mStateStackTopIndex].state;
+ State orgState = mStateStack[mStateStackTopIndex].state;
mProcessedMessages.add(msg, curStateInfo.state, orgState);
} else {
mProcessedMessages.add(msg, null, null);
@@ -892,7 +892,7 @@
private final void invokeExitMethods(StateInfo commonStateInfo) {
while ((mStateStackTopIndex >= 0) &&
(mStateStack[mStateStackTopIndex] != commonStateInfo)) {
- HierarchicalState curState = mStateStack[mStateStackTopIndex].state;
+ State curState = mStateStack[mStateStackTopIndex].state;
if (mDbg) Log.d(TAG, "invokeExitMethods: " + curState.getName());
curState.exit();
mStateStack[mStateStackTopIndex].active = false;
@@ -934,7 +934,7 @@
* reversing the order of the items on the temporary stack as
* they are moved.
*
- * @return index into mStateState where entering needs to start
+ * @return index into mStateStack where entering needs to start
*/
private final int moveTempStateStackToStateStack() {
int startingIndex = mStateStackTopIndex + 1;
@@ -967,7 +967,7 @@
* @return StateInfo of the common ancestor for the destState and
* current state or null if there is no common parent.
*/
- private final StateInfo setupTempStateStackWithStatesToEnter(HierarchicalState destState) {
+ private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
/**
* Search up the parent list of the destination state for an active
* state. Use a do while() loop as the destState must always be entered
@@ -1019,7 +1019,7 @@
/**
* @return current state
*/
- private final HierarchicalState getCurrentState() {
+ private final IState getCurrentState() {
return mStateStack[mStateStackTopIndex].state;
}
@@ -1032,7 +1032,7 @@
* @param parent the parent of state
* @return stateInfo for this state
*/
- private final StateInfo addState(HierarchicalState state, HierarchicalState parent) {
+ private final StateInfo addState(State state, State parent) {
if (mDbg) {
Log.d(TAG, "addStateInternal: E state=" + state.getName()
+ ",parent=" + ((parent == null) ? "" : parent.getName()));
@@ -1067,29 +1067,29 @@
* Constructor
*
* @param looper for dispatching messages
- * @param hsm the hierarchical state machine
+ * @param sm the hierarchical state machine
*/
- private HsmHandler(Looper looper, HierarchicalStateMachine hsm) {
+ private SmHandler(Looper looper, StateMachine sm) {
super(looper);
- mHsm = hsm;
+ mSm = sm;
addState(mHaltingState, null);
addState(mQuittingState, null);
}
- /** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */
- private final void setInitialState(HierarchicalState initialState) {
+ /** @see StateMachine#setInitialState(State) */
+ private final void setInitialState(State initialState) {
if (mDbg) Log.d(TAG, "setInitialState: initialState" + initialState.getName());
mInitialState = initialState;
}
- /** @see HierarchicalStateMachine#transitionTo(HierarchicalState) */
- private final void transitionTo(HierarchicalState destState) {
- if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + destState.getName());
- mDestState = destState;
+ /** @see StateMachine#transitionTo(IState) */
+ private final void transitionTo(IState destState) {
+ mDestState = (State) destState;
+ if (mDbg) Log.d(TAG, "StateMachine.transitionTo EX destState" + mDestState.getName());
}
- /** @see HierarchicalStateMachine#deferMessage(Message) */
+ /** @see StateMachine#deferMessage(Message) */
private final void deferMessage(Message msg) {
if (mDbg) Log.d(TAG, "deferMessage: msg=" + msg.what);
@@ -1100,51 +1100,51 @@
mDeferredMessages.add(newMsg);
}
- /** @see HierarchicalStateMachine#deferMessage(Message) */
+ /** @see StateMachine#deferMessage(Message) */
private final void quit() {
if (mDbg) Log.d(TAG, "quit:");
- sendMessage(obtainMessage(HSM_QUIT_CMD, mQuitObj));
+ sendMessage(obtainMessage(SM_QUIT_CMD, mQuitObj));
}
- /** @see HierarchicalStateMachine#isQuit(Message) */
+ /** @see StateMachine#isQuit(Message) */
private final boolean isQuit(Message msg) {
- return (msg.what == HSM_QUIT_CMD) && (msg.obj == mQuitObj);
+ return (msg.what == SM_QUIT_CMD) && (msg.obj == mQuitObj);
}
- /** @see HierarchicalStateMachine#isDbg() */
+ /** @see StateMachine#isDbg() */
private final boolean isDbg() {
return mDbg;
}
- /** @see HierarchicalStateMachine#setDbg(boolean) */
+ /** @see StateMachine#setDbg(boolean) */
private final void setDbg(boolean dbg) {
mDbg = dbg;
}
- /** @see HierarchicalStateMachine#setProcessedMessagesSize(int) */
+ /** @see StateMachine#setProcessedMessagesSize(int) */
private final void setProcessedMessagesSize(int maxSize) {
mProcessedMessages.setSize(maxSize);
}
- /** @see HierarchicalStateMachine#getProcessedMessagesSize() */
+ /** @see StateMachine#getProcessedMessagesSize() */
private final int getProcessedMessagesSize() {
return mProcessedMessages.size();
}
- /** @see HierarchicalStateMachine#getProcessedMessagesCount() */
+ /** @see StateMachine#getProcessedMessagesCount() */
private final int getProcessedMessagesCount() {
return mProcessedMessages.count();
}
- /** @see HierarchicalStateMachine#getProcessedMessageInfo(int) */
+ /** @see StateMachine#getProcessedMessageInfo(int) */
private final ProcessedMessageInfo getProcessedMessageInfo(int index) {
return mProcessedMessages.get(index);
}
}
- private HsmHandler mHsmHandler;
- private HandlerThread mHsmThread;
+ private SmHandler mSmHandler;
+ private HandlerThread mSmThread;
/**
* Initialize.
@@ -1154,28 +1154,28 @@
*/
private void initStateMachine(String name, Looper looper) {
mName = name;
- mHsmHandler = new HsmHandler(looper, this);
+ mSmHandler = new SmHandler(looper, this);
}
/**
- * Constructor creates an HSM with its own thread.
+ * Constructor creates a StateMachine with its own thread.
*
* @param name of the state machine
*/
- protected HierarchicalStateMachine(String name) {
- mHsmThread = new HandlerThread(name);
- mHsmThread.start();
- Looper looper = mHsmThread.getLooper();
+ protected StateMachine(String name) {
+ mSmThread = new HandlerThread(name);
+ mSmThread.start();
+ Looper looper = mSmThread.getLooper();
initStateMachine(name, looper);
}
/**
- * Constructor creates an HSMStateMachine using the looper.
+ * Constructor creates an StateMachine using the looper.
*
* @param name of the state machine
*/
- protected HierarchicalStateMachine(String name, Looper looper) {
+ protected StateMachine(String name, Looper looper) {
initStateMachine(name, looper);
}
@@ -1184,30 +1184,30 @@
* @param state the state to add
* @param parent the parent of state
*/
- protected final void addState(HierarchicalState state, HierarchicalState parent) {
- mHsmHandler.addState(state, parent);
+ protected final void addState(State state, State parent) {
+ mSmHandler.addState(state, parent);
}
/**
* @return current message
*/
protected final Message getCurrentMessage() {
- return mHsmHandler.getCurrentMessage();
+ return mSmHandler.getCurrentMessage();
}
/**
* @return current state
*/
- protected final HierarchicalState getCurrentState() {
- return mHsmHandler.getCurrentState();
+ protected final IState getCurrentState() {
+ return mSmHandler.getCurrentState();
}
/**
* Add a new state to the state machine, parent will be null
* @param state to add
*/
- protected final void addState(HierarchicalState state) {
- mHsmHandler.addState(state, null);
+ protected final void addState(State state) {
+ mSmHandler.addState(state, null);
}
/**
@@ -1216,8 +1216,8 @@
*
* @param initialState is the state which will receive the first message.
*/
- protected final void setInitialState(HierarchicalState initialState) {
- mHsmHandler.setInitialState(initialState);
+ protected final void setInitialState(State initialState) {
+ mSmHandler.setInitialState(initialState);
}
/**
@@ -1228,8 +1228,8 @@
*
* @param destState will be the state that receives the next message.
*/
- protected final void transitionTo(HierarchicalState destState) {
- mHsmHandler.transitionTo(destState);
+ protected final void transitionTo(IState destState) {
+ mSmHandler.transitionTo(destState);
}
/**
@@ -1240,7 +1240,7 @@
* will be called.
*/
protected final void transitionToHaltingState() {
- mHsmHandler.transitionTo(mHsmHandler.mHaltingState);
+ mSmHandler.transitionTo(mSmHandler.mHaltingState);
}
/**
@@ -1253,7 +1253,7 @@
* @param msg is deferred until the next transition.
*/
protected final void deferMessage(Message msg) {
- mHsmHandler.deferMessage(msg);
+ mSmHandler.deferMessage(msg);
}
@@ -1263,7 +1263,7 @@
* @param msg that couldn't be handled.
*/
protected void unhandledMessage(Message msg) {
- if (mHsmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
+ if (mSmHandler.mDbg) Log.e(TAG, mName + " - unhandledMessage: msg.what=" + msg.what);
}
/**
@@ -1276,15 +1276,15 @@
/**
* This will be called once after handling a message that called
* transitionToHalting. All subsequent messages will invoke
- * {@link HierarchicalStateMachine#haltedProcessMessage(Message)}
+ * {@link StateMachine#haltedProcessMessage(Message)}
*/
protected void halting() {
}
/**
* This will be called once after a quit message that was NOT handled by
- * the derived HSM. The HSM will stop and any subsequent messages will be
- * ignored. In addition, if this HSM created the thread, the thread will
+ * the derived StateMachine. The StateMachine will stop and any subsequent messages will be
+ * ignored. In addition, if this StateMachine created the thread, the thread will
* be stopped after this method returns.
*/
protected void quitting() {
@@ -1303,35 +1303,35 @@
* @param maxSize number of messages to maintain at anyone time.
*/
public final void setProcessedMessagesSize(int maxSize) {
- mHsmHandler.setProcessedMessagesSize(maxSize);
+ mSmHandler.setProcessedMessagesSize(maxSize);
}
/**
* @return number of messages processed
*/
public final int getProcessedMessagesSize() {
- return mHsmHandler.getProcessedMessagesSize();
+ return mSmHandler.getProcessedMessagesSize();
}
/**
* @return the total number of messages processed
*/
public final int getProcessedMessagesCount() {
- return mHsmHandler.getProcessedMessagesCount();
+ return mSmHandler.getProcessedMessagesCount();
}
/**
* @return a processed message information
*/
public final ProcessedMessageInfo getProcessedMessageInfo(int index) {
- return mHsmHandler.getProcessedMessageInfo(index);
+ return mSmHandler.getProcessedMessageInfo(index);
}
/**
* @return Handler
*/
public final Handler getHandler() {
- return mHsmHandler;
+ return mSmHandler;
}
/**
@@ -1341,7 +1341,7 @@
*/
public final Message obtainMessage()
{
- return Message.obtain(mHsmHandler);
+ return Message.obtain(mSmHandler);
}
/**
@@ -1351,7 +1351,7 @@
* @return message
*/
public final Message obtainMessage(int what) {
- return Message.obtain(mHsmHandler, what);
+ return Message.obtain(mSmHandler, what);
}
/**
@@ -1364,7 +1364,7 @@
*/
public final Message obtainMessage(int what, Object obj)
{
- return Message.obtain(mHsmHandler, what, obj);
+ return Message.obtain(mSmHandler, what, obj);
}
/**
@@ -1378,7 +1378,7 @@
*/
public final Message obtainMessage(int what, int arg1, int arg2)
{
- return Message.obtain(mHsmHandler, what, arg1, arg2);
+ return Message.obtain(mSmHandler, what, arg1, arg2);
}
/**
@@ -1393,107 +1393,107 @@
*/
public final Message obtainMessage(int what, int arg1, int arg2, Object obj)
{
- return Message.obtain(mHsmHandler, what, arg1, arg2, obj);
+ return Message.obtain(mSmHandler, what, arg1, arg2, obj);
}
/**
* Enqueue a message to this state machine.
*/
public final void sendMessage(int what) {
- mHsmHandler.sendMessage(obtainMessage(what));
+ mSmHandler.sendMessage(obtainMessage(what));
}
/**
* Enqueue a message to this state machine.
*/
public final void sendMessage(int what, Object obj) {
- mHsmHandler.sendMessage(obtainMessage(what,obj));
+ mSmHandler.sendMessage(obtainMessage(what,obj));
}
/**
* Enqueue a message to this state machine.
*/
public final void sendMessage(Message msg) {
- mHsmHandler.sendMessage(msg);
+ mSmHandler.sendMessage(msg);
}
/**
* Enqueue a message to this state machine after a delay.
*/
public final void sendMessageDelayed(int what, long delayMillis) {
- mHsmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
+ mSmHandler.sendMessageDelayed(obtainMessage(what), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
*/
public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
- mHsmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
+ mSmHandler.sendMessageDelayed(obtainMessage(what, obj), delayMillis);
}
/**
* Enqueue a message to this state machine after a delay.
*/
public final void sendMessageDelayed(Message msg, long delayMillis) {
- mHsmHandler.sendMessageDelayed(msg, delayMillis);
+ mSmHandler.sendMessageDelayed(msg, delayMillis);
}
/**
* Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of HierarchicalStateMachine.
+ * Protected, may only be called by instances of StateMachine.
*/
protected final void sendMessageAtFrontOfQueue(int what, Object obj) {
- mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
+ mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what, obj));
}
/**
* Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of HierarchicalStateMachine.
+ * Protected, may only be called by instances of StateMachine.
*/
protected final void sendMessageAtFrontOfQueue(int what) {
- mHsmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
+ mSmHandler.sendMessageAtFrontOfQueue(obtainMessage(what));
}
/**
* Enqueue a message to the front of the queue for this state machine.
- * Protected, may only be called by instances of HierarchicalStateMachine.
+ * Protected, may only be called by instances of StateMachine.
*/
protected final void sendMessageAtFrontOfQueue(Message msg) {
- mHsmHandler.sendMessageAtFrontOfQueue(msg);
+ mSmHandler.sendMessageAtFrontOfQueue(msg);
}
/**
* Removes a message from the message queue.
- * Protected, may only be called by instances of HierarchicalStateMachine.
+ * Protected, may only be called by instances of StateMachine.
*/
protected final void removeMessages(int what) {
- mHsmHandler.removeMessages(what);
+ mSmHandler.removeMessages(what);
}
/**
* Conditionally quit the looper and stop execution.
*
- * This sends the HSM_QUIT_MSG to the state machine and
+ * This sends the SM_QUIT_MSG to the state machine and
* if not handled by any state's processMessage then the
* state machine will be stopped and no further messages
* will be processed.
*/
public final void quit() {
- mHsmHandler.quit();
+ mSmHandler.quit();
}
/**
* @return ture if msg is quit
*/
protected final boolean isQuit(Message msg) {
- return mHsmHandler.isQuit(msg);
+ return mSmHandler.isQuit(msg);
}
/**
* @return if debugging is enabled
*/
public boolean isDbg() {
- return mHsmHandler.isDbg();
+ return mSmHandler.isDbg();
}
/**
@@ -1502,7 +1502,7 @@
* @param dbg is true to enable debugging.
*/
public void setDbg(boolean dbg) {
- mHsmHandler.setDbg(dbg);
+ mSmHandler.setDbg(dbg);
}
/**
@@ -1510,6 +1510,6 @@
*/
public void start() {
/** Send the complete construction message */
- mHsmHandler.completeConstruction();
+ mSmHandler.completeConstruction();
}
}
diff --git a/core/java/com/android/internal/view/StandaloneActionMode.java b/core/java/com/android/internal/view/StandaloneActionMode.java
index 2d067da..b54daba 100644
--- a/core/java/com/android/internal/view/StandaloneActionMode.java
+++ b/core/java/com/android/internal/view/StandaloneActionMode.java
@@ -135,6 +135,6 @@
public void onMenuModeChange(MenuBuilder menu) {
invalidate();
- mContextView.openOverflowMenu();
+ mContextView.showOverflowMenu();
}
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index ca1aa0b..beacf75 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -28,7 +28,7 @@
* @hide
*/
public class ActionMenuItemView extends LinearLayout
- implements MenuView.ItemView, View.OnClickListener {
+ implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
@@ -137,4 +137,12 @@
public boolean showsIcon() {
return true;
}
+
+ public boolean needsDividerBefore() {
+ return hasText() && mItemData.getIcon() == null;
+ }
+
+ public boolean needsDividerAfter() {
+ return hasText();
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
new file mode 100644
index 0000000..a05fa53
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java
@@ -0,0 +1,441 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.ActionMenuView.ActionMenuChildView;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.view.MenuItem;
+import android.view.SoundEffectConstants;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for building action menus as seen in the action bar and action modes.
+ */
+public class ActionMenuPresenter extends BaseMenuPresenter {
+ private View mOverflowButton;
+ private boolean mReserveOverflow;
+ private int mWidthLimit;
+ private int mActionItemWidthLimit;
+ private int mMaxItems;
+
+ // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
+ private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
+
+ private View mScrapActionButtonView;
+
+ private OverflowPopup mOverflowPopup;
+ private ActionButtonSubmenu mActionButtonPopup;
+
+ private OpenOverflowRunnable mPostedOpenRunnable;
+
+ public ActionMenuPresenter() {
+ super(com.android.internal.R.layout.action_menu_layout,
+ com.android.internal.R.layout.action_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ super.initForMenu(context, menu);
+
+ final Resources res = context.getResources();
+ final int screen = res.getConfiguration().screenLayout;
+ // TODO Use the no-buttons specifier instead here
+ mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+ Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
+
+ // Measure for initial configuration
+ mMaxItems = res.getInteger(com.android.internal.R.integer.max_action_buttons);
+
+ int width = mWidthLimit;
+ if (mReserveOverflow) {
+ OverflowMenuButton button = new OverflowMenuButton(mContext);
+ mOverflowButton = button;
+ final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ mOverflowButton.measure(spec, spec);
+ width -= mOverflowButton.getMeasuredWidth();
+ } else {
+ mOverflowButton = null;
+ }
+
+ mActionItemWidthLimit = width;
+
+ // Drop a scrap view as it may no longer reflect the proper context/config.
+ mScrapActionButtonView = null;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ MenuView result = super.getMenuView(root);
+ ((ActionMenuView) result).setPresenter(this);
+ return result;
+ }
+
+ @Override
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ final View actionView = item.getActionView();
+ return actionView != null ? actionView : super.getItemView(item, convertView, parent);
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {
+ itemView.initialize(item, 0);
+ ((ActionMenuItemView) itemView).setItemInvoker((ActionMenuView) mMenuView);
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return item.isActionButton();
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ super.updateMenuView(cleared);
+
+ if (mReserveOverflow && mMenu.getNonActionItems().size() > 0) {
+ if (mOverflowButton == null) {
+ mOverflowButton = new OverflowMenuButton(mContext);
+ }
+ ViewGroup parent = (ViewGroup) mOverflowButton.getParent();
+ if (parent != mMenuView) {
+ if (parent != null) {
+ parent.removeView(mOverflowButton);
+ }
+ ((ViewGroup) mMenuView).addView(mOverflowButton);
+ }
+ } else if (mOverflowButton != null && mOverflowButton.getParent() == mMenuView) {
+ ((ViewGroup) mMenuView).removeView(mOverflowButton);
+ }
+ }
+
+ @Override
+ public boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) == mOverflowButton) return false;
+ return super.filterLeftoverView(parent, childIndex);
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ SubMenuBuilder topSubMenu = subMenu;
+ while (topSubMenu.getParentMenu() != mMenu) {
+ topSubMenu = (SubMenuBuilder) topSubMenu.getParentMenu();
+ }
+ View anchor = findViewForItem(topSubMenu.getItem());
+ if (anchor == null) return false;
+
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
+ mActionButtonPopup.setAnchorView(anchor);
+ mActionButtonPopup.show();
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ private View findViewForItem(MenuItem item) {
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ if (parent == null) return null;
+
+ final int count = parent.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = parent.getChildAt(i);
+ if (child instanceof MenuView.ItemView &&
+ ((MenuView.ItemView) child).getItemData() == item) {
+ return child;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Display the overflow menu if one is present.
+ * @return true if the overflow menu was shown, false otherwise.
+ */
+ public boolean showOverflowMenu() {
+ if (mReserveOverflow && !isOverflowMenuShowing() && mMenuView != null &&
+ mPostedOpenRunnable == null) {
+ Log.d("ActionMenuPresenter", "showOverflowMenu");
+ OverflowPopup popup = new OverflowPopup(mContext, mMenu, mOverflowButton, true);
+ mPostedOpenRunnable = new OpenOverflowRunnable(popup);
+ // Post this for later; we might still need a layout for the anchor to be right.
+ ((View) mMenuView).post(mPostedOpenRunnable);
+
+ // ActionMenuPresenter uses null as a callback argument here
+ // to indicate overflow is opening.
+ super.onSubMenuSelected(null);
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Hide the overflow menu if it is currently showing.
+ *
+ * @return true if the overflow menu was hidden, false otherwise.
+ */
+ public boolean hideOverflowMenu() {
+ if (mPostedOpenRunnable != null && mMenuView != null) {
+ ((View) mMenuView).removeCallbacks(mPostedOpenRunnable);
+ return true;
+ }
+
+ MenuPopupHelper popup = mOverflowPopup;
+ if (popup != null) {
+ popup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Dismiss all popup menus - overflow and submenus.
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean dismissPopupMenus() {
+ boolean result = hideOverflowMenu();
+ result |= hideSubMenus();
+ return result;
+ }
+
+ /**
+ * Dismiss all submenu popups.
+ *
+ * @return true if popups were dismissed, false otherwise. (This can be because none were open.)
+ */
+ public boolean hideSubMenus() {
+ if (mActionButtonPopup != null) {
+ mActionButtonPopup.dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return true if the overflow menu is currently showing
+ */
+ public boolean isOverflowMenuShowing() {
+ return mOverflowPopup != null && mOverflowPopup.isShowing();
+ }
+
+ /**
+ * @return true if space has been reserved in the action menu for an overflow item.
+ */
+ public boolean isOverflowReserved() {
+ return mReserveOverflow;
+ }
+
+ public boolean flagActionItems() {
+ final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ int maxActions = mMaxItems;
+ int widthLimit = mActionItemWidthLimit;
+ final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final ViewGroup parent = (ViewGroup) mMenuView;
+
+ int requiredItems = 0;
+ int requestedItems = 0;
+ int firstActionWidth = 0;
+ boolean hasOverflow = false;
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.requiresActionButton()) {
+ requiredItems++;
+ } else if (item.requestsActionButton()) {
+ requestedItems++;
+ } else {
+ hasOverflow = true;
+ }
+ }
+
+ // Reserve a spot for the overflow item if needed.
+ if (mReserveOverflow &&
+ (hasOverflow || requiredItems + requestedItems > maxActions)) {
+ maxActions--;
+ }
+ maxActions -= requiredItems;
+
+ final SparseBooleanArray seenGroups = mActionButtonGroups;
+ seenGroups.clear();
+
+ // Flag as many more requested items as will fit.
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+
+ if (item.requiresActionButton()) {
+ View v = item.getActionView();
+ if (v == null) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+ final int groupId = item.getGroupId();
+ if (groupId != 0) {
+ seenGroups.put(groupId, true);
+ }
+ } else if (item.requestsActionButton()) {
+ // Items in a group with other items that already have an action slot
+ // can break the max actions rule, but not the width limit.
+ final int groupId = item.getGroupId();
+ final boolean inGroup = seenGroups.get(groupId);
+ boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
+ maxActions--;
+
+ if (isAction) {
+ View v = item.getActionView();
+ if (v == null) {
+ v = getItemView(item, mScrapActionButtonView, parent);
+ if (mScrapActionButtonView == null) {
+ mScrapActionButtonView = v;
+ }
+ }
+ v.measure(querySpec, querySpec);
+ final int measuredWidth = v.getMeasuredWidth();
+ widthLimit -= measuredWidth;
+ if (firstActionWidth == 0) {
+ firstActionWidth = measuredWidth;
+ }
+
+ // Did this push the entire first item past halfway?
+ if (widthLimit + firstActionWidth <= 0) {
+ isAction = false;
+ }
+ }
+
+ if (isAction && groupId != 0) {
+ seenGroups.put(groupId, true);
+ } else if (inGroup) {
+ // We broke the width limit. Demote the whole group, they all overflow now.
+ seenGroups.put(groupId, false);
+ for (int j = 0; j < i; j++) {
+ MenuItemImpl areYouMyGroupie = visibleItems.get(j);
+ if (areYouMyGroupie.getGroupId() == groupId) {
+ areYouMyGroupie.setIsActionButton(false);
+ }
+ }
+ }
+
+ item.setIsActionButton(isAction);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ dismissPopupMenus();
+ super.onCloseMenu(menu, allMenusAreClosing);
+ }
+
+ private class OverflowMenuButton extends ImageButton implements ActionMenuChildView {
+ public OverflowMenuButton(Context context) {
+ super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
+
+ setClickable(true);
+ setFocusable(true);
+ setVisibility(VISIBLE);
+ setEnabled(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ if (super.performClick()) {
+ return true;
+ }
+
+ playSoundEffect(SoundEffectConstants.CLICK);
+ showOverflowMenu();
+ return true;
+ }
+
+ public boolean needsDividerBefore() {
+ return true;
+ }
+
+ public boolean needsDividerAfter() {
+ return false;
+ }
+ }
+
+ private class OverflowPopup extends MenuPopupHelper {
+ public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
+ boolean overflowOnly) {
+ super(context, menu, anchorView, overflowOnly);
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mMenu.close();
+ mOverflowPopup = null;
+ }
+ }
+
+ private class ActionButtonSubmenu extends MenuPopupHelper {
+ private SubMenuBuilder mSubMenu;
+
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
+ super(context, subMenu);
+ mSubMenu = subMenu;
+
+ MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
+ if (!item.isActionButton()) {
+ // Give a reasonable anchor to nested submenus.
+ setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
+ }
+ }
+
+ @Override
+ public void onDismiss() {
+ super.onDismiss();
+ mSubMenu.close();
+ mActionButtonPopup = null;
+ }
+ }
+
+ private class OpenOverflowRunnable implements Runnable {
+ private OverflowPopup mPopup;
+
+ public OpenOverflowRunnable(OverflowPopup popup) {
+ mPopup = popup;
+ }
+
+ public void run() {
+ mMenu.changeMenuMode();
+ if (mPopup.tryShow()) {
+ mOverflowPopup = mPopup;
+ mPostedOpenRunnable = null;
+ }
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuView.java b/core/java/com/android/internal/view/menu/ActionMenuView.java
index 7775f00..0ea9c89 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuView.java
@@ -17,63 +17,22 @@
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
-import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import java.util.ArrayList;
-
/**
* @hide
*/
public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvoker, MenuView {
private static final String TAG = "ActionMenuView";
-
- // TODO Theme/style this.
- private static final int DIVIDER_PADDING = 12; // dips
private MenuBuilder mMenu;
- private int mMaxItems;
- private int mWidthLimit;
private boolean mReserveOverflow;
- private OverflowMenuButton mOverflowButton;
- private MenuPopupHelper mOverflowPopup;
-
- private float mDividerPadding;
-
- private Drawable mDivider;
-
- private final Runnable mShowOverflow = new Runnable() {
- public void run() {
- showOverflowMenu();
- }
- };
-
- private class OpenOverflowRunnable implements Runnable {
- private MenuPopupHelper mPopup;
-
- public OpenOverflowRunnable(MenuPopupHelper popup) {
- mPopup = popup;
- }
-
- public void run() {
- if (mPopup.tryShow()) {
- mOverflowPopup = mPopup;
- mPostedOpenRunnable = null;
- }
- }
- }
-
- private OpenOverflowRunnable mPostedOpenRunnable;
+ private ActionMenuPresenter mPresenter;
public ActionMenuView(Context context) {
this(context, null);
@@ -81,60 +40,28 @@
public ActionMenuView(Context context, AttributeSet attrs) {
super(context, attrs);
-
- final Resources res = getResources();
-
- // Measure for initial configuration
- mMaxItems = getMaxActionButtons();
-
- // TODO There has to be a better way to indicate that we don't have a hard menu key.
- final int screen = res.getConfiguration().screenLayout;
- mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
- Configuration.SCREENLAYOUT_SIZE_XLARGE;
- mWidthLimit = res.getDisplayMetrics().widthPixels / 2;
-
- TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
- mDivider = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
- a.recycle();
-
- mDividerPadding = DIVIDER_PADDING * res.getDisplayMetrics().density;
-
setBaselineAligned(false);
}
+ public void setPresenter(ActionMenuPresenter presenter) {
+ mPresenter = presenter;
+ }
+
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- final int screen = newConfig.screenLayout;
- mReserveOverflow = (screen & Configuration.SCREENLAYOUT_SIZE_MASK) ==
- Configuration.SCREENLAYOUT_SIZE_XLARGE;
- mMaxItems = getMaxActionButtons();
- mWidthLimit = getResources().getDisplayMetrics().widthPixels / 2;
- if (mMenu != null) {
- mMenu.setMaxActionItems(mMaxItems);
- updateChildren(false);
- }
+ mPresenter.updateMenuView(false);
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
- post(mShowOverflow);
+ if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
}
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
- if (mOverflowPopup != null && mOverflowPopup.isShowing()) {
- mOverflowPopup.dismiss();
- }
- removeCallbacks(mShowOverflow);
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
- }
- }
-
- private int getMaxActionButtons() {
- return getResources().getInteger(com.android.internal.R.integer.max_action_buttons);
+ mPresenter.dismissPopupMenus();
}
public boolean isOverflowReserved() {
@@ -144,10 +71,6 @@
public void setOverflowReserved(boolean reserveOverflow) {
mReserveOverflow = reserveOverflow;
}
-
- public View getOverflowButton() {
- return mOverflowButton;
- }
@Override
protected LayoutParams generateDefaultLayoutParams() {
@@ -169,6 +92,11 @@
return generateDefaultLayoutParams();
}
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof LayoutParams;
+ }
+
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
@@ -177,243 +105,26 @@
return 0;
}
- public void initialize(MenuBuilder menu, int menuType) {
- int width = mWidthLimit;
- if (mReserveOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- final int spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- mOverflowButton.measure(spec, spec);
- width -= mOverflowButton.getMeasuredWidth();
- }
-
- menu.setActionWidthLimit(width);
-
- menu.setMaxActionItems(mMaxItems);
- final boolean cleared = mMenu != menu;
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(cleared);
}
- public void updateChildren(boolean cleared) {
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getActionItems(mReserveOverflow);
- final int itemCount = itemsToShow.size();
-
- boolean needsDivider = false;
- int childIndex = 0;
- for (int i = 0; i < itemCount; i++) {
- final MenuItemImpl itemData = itemsToShow.get(i);
- boolean hasDivider = false;
-
- if (needsDivider) {
- if (!isDivider(getChildAt(childIndex))) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- hasDivider = true;
- childIndex++;
- }
-
- View childToAdd = itemData.getActionView();
- boolean needsPreDivider = false;
- if (childToAdd != null) {
- childToAdd.setLayoutParams(makeActionViewLayoutParams(childToAdd));
- } else {
- ActionMenuItemView view = (ActionMenuItemView) itemData.getItemView(
- MenuBuilder.TYPE_ACTION_BUTTON, this);
- view.setItemInvoker(this);
- needsPreDivider = i > 0 && !hasDivider && view.hasText() &&
- itemData.getIcon() == null;
- needsDivider = view.hasText();
- childToAdd = view;
- }
-
- boolean addPreDivider = removeChildrenUntil(childIndex, childToAdd, needsPreDivider);
-
- if (addPreDivider) addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- if (needsPreDivider) childIndex++;
-
- if (getChildAt(childIndex) != childToAdd) {
- addView(childToAdd, childIndex);
- }
- childIndex++;
+ @Override
+ protected boolean hasDividerBeforeChildAt(int childIndex) {
+ final View childBefore = getChildAt(childIndex - 1);
+ final View child = getChildAt(childIndex);
+ boolean result = false;
+ if (childIndex < getChildCount() && childBefore instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) childBefore).needsDividerAfter();
}
-
- final boolean hasOverflow = mOverflowButton != null && mOverflowButton.getParent() == this;
- final boolean needsOverflow = mReserveOverflow && mMenu.getNonActionItems(true).size() > 0;
-
- if (hasOverflow != needsOverflow) {
- if (needsOverflow) {
- if (mOverflowButton == null) {
- OverflowMenuButton button = new OverflowMenuButton(mContext);
- mOverflowButton = button;
- }
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton, true);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- childIndex++;
- }
- addView(mOverflowButton, childIndex);
- childIndex++;
- } else {
- removeView(mOverflowButton);
- }
- } else {
- if (needsOverflow) {
- boolean overflowDivider = itemCount > 0;
- boolean addDivider = removeChildrenUntil(childIndex, mOverflowButton,
- overflowDivider);
- if (addDivider && itemCount > 0) {
- addView(makeDividerView(), childIndex, makeDividerLayoutParams());
- }
- if (overflowDivider) {
- childIndex += 2;
- } else {
- childIndex++;
- }
- }
+ if (childIndex > 0 && child instanceof ActionMenuChildView) {
+ result |= ((ActionMenuChildView) child).needsDividerBefore();
}
-
- while (getChildCount() > childIndex) {
- removeViewAt(childIndex);
- }
- }
-
- private boolean removeChildrenUntil(int start, View targetChild, boolean needsPreDivider) {
- final int childCount = getChildCount();
- boolean found = false;
- for (int i = start; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child == targetChild) {
- found = true;
- break;
- }
- }
-
- if (!found) {
- return needsPreDivider;
- }
-
- for (int i = start; i < getChildCount(); ) {
- final View child = getChildAt(i);
- if (needsPreDivider && isDivider(child)) {
- needsPreDivider = false;
- i++;
- continue;
- }
- if (child == targetChild) break;
- removeViewAt(i);
- }
-
- return needsPreDivider;
- }
-
- private static boolean isDivider(View v) {
- return v != null && v.getId() == com.android.internal.R.id.action_menu_divider;
- }
-
- public boolean showOverflowMenu() {
- if (mOverflowButton != null && !isOverflowMenuShowing()) {
- mMenu.getCallback().onMenuModeChange(mMenu);
- return true;
- }
- return false;
- }
-
- public void openOverflowMenu() {
- OverflowPopup popup = new OverflowPopup(getContext(), mMenu, mOverflowButton, true);
- mPostedOpenRunnable = new OpenOverflowRunnable(popup);
- // Post this for later; we might still need a layout for the anchor to be right.
- post(mPostedOpenRunnable);
- }
-
- public boolean isOverflowMenuShowing() {
- return mOverflowPopup != null && mOverflowPopup.isShowing();
- }
-
- public boolean isOverflowMenuOpen() {
- return mOverflowPopup != null;
- }
-
- public boolean hideOverflowMenu() {
- if (mPostedOpenRunnable != null) {
- removeCallbacks(mPostedOpenRunnable);
- return true;
- }
-
- MenuPopupHelper popup = mOverflowPopup;
- if (popup != null) {
- popup.dismiss();
- return true;
- }
- return false;
- }
-
- private boolean addItemView(boolean needsDivider, ActionMenuItemView view) {
- view.setItemInvoker(this);
- boolean hasText = view.hasText();
-
- if (hasText && needsDivider) {
- addView(makeDividerView(), makeDividerLayoutParams());
- }
- addView(view);
- return hasText;
- }
-
- private ImageView makeDividerView() {
- ImageView result = new ImageView(mContext);
- result.setImageDrawable(mDivider);
- result.setScaleType(ImageView.ScaleType.FIT_XY);
- result.setId(com.android.internal.R.id.action_menu_divider);
return result;
}
- private LayoutParams makeDividerLayoutParams() {
- LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.MATCH_PARENT);
- params.topMargin = (int) mDividerPadding;
- params.bottomMargin = (int) mDividerPadding;
- return params;
- }
-
- private LayoutParams makeActionViewLayoutParams(View view) {
- return generateLayoutParams(view.getLayoutParams());
- }
-
- private class OverflowMenuButton extends ImageButton {
- public OverflowMenuButton(Context context) {
- super(context, null, com.android.internal.R.attr.actionOverflowButtonStyle);
-
- setClickable(true);
- setFocusable(true);
- setVisibility(VISIBLE);
- setEnabled(true);
- }
-
- @Override
- public boolean performClick() {
- if (super.performClick()) {
- return true;
- }
-
- playSoundEffect(SoundEffectConstants.CLICK);
- showOverflowMenu();
- return true;
- }
- }
-
- private class OverflowPopup extends MenuPopupHelper {
- public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly) {
- super(context, menu, anchorView, overflowOnly);
- }
-
- @Override
- public void onDismiss() {
- super.onDismiss();
- mMenu.getCallback().onCloseMenu(mMenu, true);
- mOverflowPopup = null;
- }
+ public interface ActionMenuChildView {
+ public boolean needsDividerBefore();
+ public boolean needsDividerAfter();
}
}
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
new file mode 100644
index 0000000..71511c6
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -0,0 +1,191 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for MenuPresenters that have a consistent container view and item
+ * views. Behaves similarly to an AdapterView in that existing item views will
+ * be reused if possible when items change.
+ */
+public abstract class BaseMenuPresenter implements MenuPresenter {
+ protected Context mContext;
+ protected MenuBuilder mMenu;
+ protected LayoutInflater mInflater;
+ private Callback mCallback;
+
+ private int mMenuLayoutRes;
+ private int mItemLayoutRes;
+
+ protected MenuView mMenuView;
+
+ /**
+ * Construct a new BaseMenuPresenter.
+ *
+ * @param menuLayoutRes Layout resource ID for the menu container view
+ * @param itemLayoutRes Layout resource ID for a single item view
+ */
+ public BaseMenuPresenter(int menuLayoutRes, int itemLayoutRes) {
+ mMenuLayoutRes = menuLayoutRes;
+ mItemLayoutRes = itemLayoutRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = context;
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (MenuView) mInflater.inflate(mMenuLayoutRes, root, false);
+ mMenuView.initialize(mMenu);
+ updateMenuView(true);
+ }
+
+ return mMenuView;
+ }
+
+ /**
+ * Reuses item views when it can
+ */
+ public void updateMenuView(boolean cleared) {
+ mMenu.flagActionItems();
+ ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
+ final int itemCount = visibleItems.size();
+ final ViewGroup parent = (ViewGroup) mMenuView;
+ int childIndex = 0;
+ for (int i = 0; i < itemCount; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (shouldIncludeItem(childIndex, item)) {
+ final View convertView = parent.getChildAt(childIndex);
+ final View itemView = getItemView(item, convertView, parent);
+ if (itemView != convertView) {
+ addItemView(itemView, childIndex);
+ }
+ childIndex++;
+ }
+ }
+
+ // Remove leftover views.
+ while (childIndex < parent.getChildCount()) {
+ if (!filterLeftoverView(parent, childIndex)) {
+ childIndex++;
+ }
+ }
+ }
+
+ /**
+ * Add an item view at the given index.
+ *
+ * @param itemView View to add
+ * @param childIndex Index within the parent to insert at
+ */
+ protected void addItemView(View itemView, int childIndex) {
+ ((ViewGroup) mMenuView).addView(itemView, childIndex);
+ }
+
+ /**
+ * Filter the child view at index and remove it if appropriate.
+ * @param parent Parent to filter from
+ * @param childIndex Index to filter
+ * @return true if the child view at index was removed
+ */
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ parent.removeViewAt(childIndex);
+ return true;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ /**
+ * Create a new item view that can be re-bound to other item data later.
+ *
+ * @return The new item view
+ */
+ public MenuView.ItemView createItemView(ViewGroup parent) {
+ return (MenuView.ItemView) mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ /**
+ * Prepare an item view for use. See AdapterView for the basic idea at work here.
+ * This may require creating a new item view, but well-behaved implementations will
+ * re-use the view passed as convertView if present. The returned view will be populated
+ * with data from the item parameter.
+ *
+ * @param item Item to present
+ * @param convertView Existing view to reuse
+ * @param parent Intended parent view - use for inflation.
+ * @return View that presents the requested menu item
+ */
+ public View getItemView(MenuItemImpl item, View convertView, ViewGroup parent) {
+ MenuView.ItemView itemView;
+ if (convertView instanceof MenuView.ItemView) {
+ itemView = (MenuView.ItemView) convertView;
+ } else {
+ itemView = createItemView(parent);
+ }
+ bindItemView(item, itemView);
+ return (View) itemView;
+ }
+
+ /**
+ * Bind item data to an existing item view.
+ *
+ * @param item Item to bind
+ * @param itemView View to populate with item data
+ */
+ public abstract void bindItemView(MenuItemImpl item, MenuView.ItemView itemView);
+
+ /**
+ * Filter item by child index and item data.
+ *
+ * @param childIndex Indended presentation index of this item
+ * @param item Item to present
+ * @return true if this item should be included in this menu presentation; false otherwise
+ */
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ return true;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ public boolean onSubMenuSelected(SubMenuBuilder menu) {
+ if (mCallback != null) {
+ return mCallback.onOpenSubMenu(menu);
+ }
+ return false;
+ }
+
+ public boolean flagActionItems() {
+ return false;
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 9e4b4ce..723ece4 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -17,17 +17,15 @@
package com.android.internal.view.menu;
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.ListAdapter;
-import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
-
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
+import android.widget.ListView;
/**
* The expanded menu view is a list-like menu with all of the available menu items. It is opened
@@ -53,23 +51,8 @@
setOnItemClickListener(this);
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
-
- setAdapter(menu.new MenuAdapter(menuType));
- }
-
- public void updateChildren(boolean cleared) {
- ListAdapter adapter = getAdapter();
- // Tell adapter of the change, it will notify the mListView
- if (adapter != null) {
- if (cleared) {
- ((BaseAdapter)adapter).notifyDataSetInvalidated();
- }
- else {
- ((BaseAdapter)adapter).notifyDataSetChanged();
- }
- }
}
@Override
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3c5b422..afa8a01 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -112,6 +112,10 @@
setEnabled(itemData.isEnabled());
}
+ public void setItemData(MenuItemImpl data) {
+ mItemData = data;
+ }
+
@Override
public boolean performClick() {
// Let the view's click listener have top priority (the More button relies on this)
diff --git a/core/java/com/android/internal/view/menu/IconMenuPresenter.java b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
new file mode 100644
index 0000000..f717904
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/IconMenuPresenter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.internal.view.menu;
+
+import com.android.internal.view.menu.MenuView.ItemView;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for the classic "six-pack" icon menu.
+ */
+public class IconMenuPresenter extends BaseMenuPresenter {
+ private IconMenuItemView mMoreView;
+ private int mMaxItems = -1;
+
+ private static final String VIEWS_TAG = "android:menu:icon";
+
+ public IconMenuPresenter() {
+ super(com.android.internal.R.layout.icon_menu_layout,
+ com.android.internal.R.layout.icon_menu_item_layout);
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ mContext = new ContextThemeWrapper(context, com.android.internal.R.style.Theme_IconMenu);
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ mMaxItems = -1;
+ }
+
+ @Override
+ public void bindItemView(MenuItemImpl item, ItemView itemView) {
+ final IconMenuItemView view = (IconMenuItemView) itemView;
+ view.setItemData(item);
+
+ view.initialize(item.getTitleForItemView(view), item.getIcon());
+
+ view.setVisibility(item.isVisible() ? View.VISIBLE : View.GONE);
+ view.setEnabled(view.isEnabled());
+ view.setLayoutParams(view.getTextAppropriateLayoutParams());
+ }
+
+ @Override
+ public boolean shouldIncludeItem(int childIndex, MenuItemImpl item) {
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ boolean fits = (itemsToShow.size() == mMaxItems && childIndex < mMaxItems) ||
+ childIndex < mMaxItems - 1;
+ return fits && !item.isActionButton();
+ }
+
+ @Override
+ protected void addItemView(View itemView, int childIndex) {
+ final IconMenuItemView v = (IconMenuItemView) itemView;
+ final IconMenuView parent = (IconMenuView) mMenuView;
+
+ v.setIconMenuView(parent);
+ v.setItemInvoker(parent);
+ v.setBackgroundDrawable(parent.getItemBackgroundDrawable());
+ super.addItemView(itemView, childIndex);
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ super.onSubMenuSelected(subMenu);
+ return true;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ final IconMenuView menuView = (IconMenuView) mMenuView;
+ if (mMaxItems < 0) mMaxItems = menuView.getMaxItems();
+ final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems();
+ final boolean needsMore = itemsToShow.size() > mMaxItems;
+ super.updateMenuView(cleared);
+
+ if (needsMore && (mMoreView == null || mMoreView.getParent() != menuView)) {
+ if (mMoreView == null) {
+ mMoreView = menuView.createMoreItemView();
+ mMoreView.setBackgroundDrawable(menuView.getItemBackgroundDrawable());
+ }
+ menuView.addView(mMoreView);
+ } else if (!needsMore && mMoreView != null) {
+ menuView.removeView(mMoreView);
+ }
+
+ menuView.setNumActualItemsShown(needsMore ? mMaxItems - 1 : itemsToShow.size());
+ }
+
+ @Override
+ protected boolean filterLeftoverView(ViewGroup parent, int childIndex) {
+ if (parent.getChildAt(childIndex) != mMoreView) {
+ return super.filterLeftoverView(parent, childIndex);
+ }
+ return false;
+ }
+
+ public int getNumActualItemsShown() {
+ return ((IconMenuView) mMenuView).getNumActualItemsShown();
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ if (viewStates != null) {
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index d18c9727..dab43eb 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -80,10 +80,7 @@
/** Icon for the 'More' button */
private Drawable mMoreIcon;
-
- /** Item view for the 'More' button */
- private IconMenuItemView mMoreItemView;
-
+
/** Background of each item (should contain the selected and focused states) */
private Drawable mItemBackground;
@@ -172,6 +169,10 @@
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
}
+ int getMaxItems() {
+ return mMaxItems;
+ }
+
/**
* Figures out the layout for the menu items.
*
@@ -277,23 +278,8 @@
return true;
}
- /**
- * Adds an IconMenuItemView to this icon menu view.
- * @param itemView The item's view to add
- */
- private void addItemView(IconMenuItemView itemView) {
- // Set ourselves on the item view
- itemView.setIconMenuView(this);
-
- // Apply the background to the item view
- itemView.setBackgroundDrawable(
- mItemBackground.getConstantState().newDrawable(
- getContext().getResources()));
-
- // This class is the invoker for all its item views
- itemView.setItemInvoker(this);
-
- addView(itemView, itemView.getTextAppropriateLayoutParams());
+ Drawable getItemBackgroundDrawable() {
+ return mItemBackground.getConstantState().newDrawable(getContext().getResources());
}
/**
@@ -302,25 +288,23 @@
* have a MenuItemData backing it.
* @return The IconMenuItemView for the 'More' button
*/
- private IconMenuItemView createMoreItemView() {
- LayoutInflater inflater = mMenu.getMenuType(MenuBuilder.TYPE_ICON).getInflater();
+ IconMenuItemView createMoreItemView() {
+ Context context = getContext();
+ LayoutInflater inflater = LayoutInflater.from(context);
final IconMenuItemView itemView = (IconMenuItemView) inflater.inflate(
com.android.internal.R.layout.icon_menu_item_layout, null);
- Resources r = getContext().getResources();
+ Resources r = context.getResources();
itemView.initialize(r.getText(com.android.internal.R.string.more_item_label), mMoreIcon);
// Set up a click listener on the view since there will be no invocation sequence
// due to the lack of a MenuItemData this view
itemView.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
- // Switches the menu to expanded mode
- MenuBuilder.Callback cb = mMenu.getCallback();
- if (cb != null) {
- // Call callback
- cb.onMenuModeChange(mMenu);
- }
+ // Switches the menu to expanded mode. Requires support from
+ // the menu's active callback.
+ mMenu.changeMenuMode();
}
});
@@ -328,51 +312,8 @@
}
- public void initialize(MenuBuilder menu, int menuType) {
+ public void initialize(MenuBuilder menu) {
mMenu = menu;
- updateChildren(true);
- }
-
- public void updateChildren(boolean cleared) {
- // This method does a clear refresh of children
- removeAllViews();
-
- // IconMenuView never wants content sorted for an overflow action button, since
- // it is never used in the presence of an overflow button.
- final ArrayList<MenuItemImpl> itemsToShow = mMenu.getNonActionItems(false);
- final int numItems = itemsToShow.size();
- final int numItemsThatCanFit = mMaxItems;
- // Minimum of the num that can fit and the num that we have
- final int minFitMinus1AndNumItems = Math.min(numItemsThatCanFit - 1, numItems);
-
- MenuItemImpl itemData;
- // Traverse through all but the last item that can fit since that last item can either
- // be a 'More' button or a sixth item
- for (int i = 0; i < minFitMinus1AndNumItems; i++) {
- itemData = itemsToShow.get(i);
- addItemView((IconMenuItemView) itemData.getItemView(MenuBuilder.TYPE_ICON, this));
- }
-
- if (numItems > numItemsThatCanFit) {
- // If there are more items than we can fit, show the 'More' button to
- // switch to expanded mode
- if (mMoreItemView == null) {
- mMoreItemView = createMoreItemView();
- }
-
- addItemView(mMoreItemView);
-
- // The last view is the more button, so the actual number of items is one less than
- // the number that can fit
- mNumActualItemsShown = numItemsThatCanFit - 1;
- } else if (numItems == numItemsThatCanFit) {
- // There are exactly the number we can show, so show the last item
- final MenuItemImpl lastItemData = itemsToShow.get(numItemsThatCanFit - 1);
- addItemView((IconMenuItemView) lastItemData.getItemView(MenuBuilder.TYPE_ICON, this));
-
- // The items shown fit exactly
- mNumActualItemsShown = numItemsThatCanFit;
- }
}
/**
@@ -463,13 +404,6 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (mHasStaleChildren) {
- mHasStaleChildren = false;
-
- // If we have stale data, resync with the menu
- updateChildren(false);
- }
-
int measuredWidth = resolveSize(Integer.MAX_VALUE, widthMeasureSpec);
calculateItemFittingMetadata(measuredWidth);
layoutItems(measuredWidth);
@@ -564,6 +498,9 @@
return mNumActualItemsShown;
}
+ void setNumActualItemsShown(int count) {
+ mNumActualItemsShown = count;
+ }
public int getWindowAnimations() {
return mAnimations;
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index 02584b6..0c3c605 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -48,6 +48,8 @@
private int mMenuType;
+ private LayoutInflater mInflater;
+
public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
@@ -187,7 +189,7 @@
}
public void setIcon(Drawable icon) {
- final boolean showIcon = mItemData.shouldShowIcon(mMenuType);
+ final boolean showIcon = mItemData.shouldShowIcon();
if (!showIcon && !mPreserveIconSpacing) {
return;
}
@@ -212,14 +214,14 @@
}
private void insertIconView() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mIconView = (ImageView) inflater.inflate(com.android.internal.R.layout.list_menu_item_icon,
this, false);
addView(mIconView, 0);
}
private void insertRadioButton() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mRadioButton =
(RadioButton) inflater.inflate(com.android.internal.R.layout.list_menu_item_radio,
this, false);
@@ -227,7 +229,7 @@
}
private void insertCheckBox() {
- LayoutInflater inflater = mItemData.getLayoutInflater(mMenuType);
+ LayoutInflater inflater = getInflater();
mCheckBox =
(CheckBox) inflater.inflate(com.android.internal.R.layout.list_menu_item_checkbox,
this, false);
@@ -242,4 +244,10 @@
return false;
}
+ private LayoutInflater getInflater() {
+ if (mInflater == null) {
+ mInflater = LayoutInflater.from(mContext);
+ }
+ return mInflater;
+ }
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuPresenter.java b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
new file mode 100644
index 0000000..2cb2a10
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/ListMenuPresenter.java
@@ -0,0 +1,202 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.SparseArray;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * MenuPresenter for list-style menus.
+ */
+public class ListMenuPresenter implements MenuPresenter, AdapterView.OnItemClickListener {
+ Context mContext;
+ LayoutInflater mInflater;
+ MenuBuilder mMenu;
+
+ ExpandedMenuView mMenuView;
+
+ private int mItemIndexOffset;
+ int mThemeRes;
+ int mItemLayoutRes;
+
+ private Callback mCallback;
+ private MenuAdapter mAdapter;
+
+ public static final String VIEWS_TAG = "android:menu:list";
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param context Context to use for theming. This will supersede the context provided
+ * to initForMenu when this presenter is added.
+ * @param itemLayoutRes Layout resource for individual item views.
+ */
+ public ListMenuPresenter(Context context, int itemLayoutRes) {
+ this(itemLayoutRes, 0);
+ mContext = context;
+ }
+
+ /**
+ * Construct a new ListMenuPresenter.
+ * @param itemLayoutRes Layout resource for individual item views.
+ * @param themeRes Resource ID of a theme to use for views.
+ */
+ public ListMenuPresenter(int itemLayoutRes, int themeRes) {
+ mItemLayoutRes = itemLayoutRes;
+ mThemeRes = themeRes;
+ }
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ if (mThemeRes != 0) {
+ mContext = new ContextThemeWrapper(context, mThemeRes);
+ } else if (mContext == null) {
+ mContext = context;
+ }
+ mInflater = LayoutInflater.from(mContext);
+ mMenu = menu;
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ if (mMenuView == null) {
+ mMenuView = (ExpandedMenuView) mInflater.inflate(
+ com.android.internal.R.layout.expanded_menu_layout, root, false);
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ mMenuView.setAdapter(mAdapter);
+ mMenuView.setOnItemClickListener(this);
+ }
+ return mMenuView;
+ }
+
+ /**
+ * Call this instead of getMenuView if you want to manage your own ListView.
+ * For proper operation, the ListView hosting this adapter should add
+ * this presenter as an OnItemClickListener.
+ *
+ * @return A ListAdapter containing the items in the menu.
+ */
+ public ListAdapter getAdapter() {
+ if (mAdapter == null) {
+ mAdapter = new MenuAdapter();
+ }
+ return mAdapter;
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (!subMenu.hasVisibleItems()) return false;
+
+ // The window manager will give us a token.
+ new MenuDialogHelper(subMenu).show(null);
+ if (mCallback != null) {
+ mCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (mCallback != null) {
+ mCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ int getItemIndexOffset() {
+ return mItemIndexOffset;
+ }
+
+ public void setItemIndexOffset(int offset) {
+ mItemIndexOffset = offset;
+ if (mMenuView != null) {
+ updateMenuView(false);
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mMenu.performItemAction(mAdapter.getItem(position), 0);
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ public void saveHierarchyState(Bundle outState) {
+ SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
+ if (mMenuView != null) {
+ ((View) mMenuView).saveHierarchyState(viewStates);
+ }
+ outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
+ }
+
+ public void restoreHierarchyState(Bundle inState) {
+ SparseArray<Parcelable> viewStates = inState.getSparseParcelableArray(VIEWS_TAG);
+ ((View) mMenuView).restoreHierarchyState(viewStates);
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.size() - mItemIndexOffset;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mMenu.getVisibleItems();
+ return items.get(position + mItemIndexOffset);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(mItemLayoutRes, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 14d0ac5..7fba5ca 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -25,29 +25,22 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
import android.os.Parcelable;
+import android.util.Log;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.ContextThemeWrapper;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
-import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Implementation of the {@link android.view.Menu} interface for creating a
@@ -55,60 +48,6 @@
*/
public class MenuBuilder implements Menu {
private static final String LOGTAG = "MenuBuilder";
-
- /** The number of different menu types */
- public static final int NUM_TYPES = 5;
- /** The menu type that represents the icon menu view */
- public static final int TYPE_ICON = 0;
- /** The menu type that represents the expanded menu view */
- public static final int TYPE_EXPANDED = 1;
- /**
- * The menu type that represents a menu dialog. Examples are context and sub
- * menus. This menu type will not have a corresponding MenuView, but it will
- * have an ItemView.
- */
- public static final int TYPE_DIALOG = 2;
- /**
- * The menu type that represents a button in the application's action bar.
- */
- public static final int TYPE_ACTION_BUTTON = 3;
- /**
- * The menu type that represents a menu popup.
- */
- public static final int TYPE_POPUP = 4;
-
- private static final String VIEWS_TAG = "android:views";
-
- private static final int THEME_SYSTEM_DEFAULT = 0;
- private static final int THEME_APPLICATION = -1;
- private static final int THEME_ALERT_DIALOG = -2;
-
- // Order must be the same order as the TYPE_*
- static final int THEME_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.style.Theme_IconMenu,
- com.android.internal.R.style.Theme_ExpandedMenu,
- THEME_ALERT_DIALOG,
- THEME_APPLICATION,
- THEME_APPLICATION,
- };
-
- // Order must be the same order as the TYPE_*
- static final int LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_layout,
- com.android.internal.R.layout.expanded_menu_layout,
- 0,
- com.android.internal.R.layout.action_menu_layout,
- 0,
- };
-
- // Order must be the same order as the TYPE_*
- static final int ITEM_LAYOUT_RES_FOR_TYPE[] = new int[] {
- com.android.internal.R.layout.icon_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.list_menu_item_layout,
- com.android.internal.R.layout.action_menu_item_layout,
- com.android.internal.R.layout.popup_menu_item_layout,
- };
private static final int[] sCategoryToOrder = new int[] {
1, /* No category */
@@ -160,14 +99,7 @@
* Contains items that should NOT appear in the Action Bar, if present.
*/
private ArrayList<MenuItemImpl> mNonActionItems;
- /**
- * The number of visible action buttons permitted in this menu
- */
- private int mMaxActionItems;
- /**
- * The total width limit in pixels for all action items within a menu
- */
- private int mActionWidthLimit;
+
/**
* Whether or not the items (or any one item's action state) has changed since it was
* last fetched.
@@ -175,12 +107,6 @@
private boolean mIsActionItemsStale;
/**
- * Whether the process of granting space as action items should reserve a space for
- * an overflow option in the action list.
- */
- private boolean mReserveActionOverflow;
-
- /**
* Default value for how added items should show in the action list.
*/
private int mDefaultShowAsAction = MenuItem.SHOW_AS_ACTION_NEVER;
@@ -210,100 +136,19 @@
* that may individually call onItemsChanged.
*/
private boolean mPreventDispatchingItemsChanged = false;
+ private boolean mItemsChangedWhileDispatchPrevented = false;
private boolean mOptionalIconsVisible = false;
- private ViewGroup mMeasureActionButtonParent;
+ private boolean mIsClosing = false;
- private final WeakReference<MenuAdapter>[] mAdapterCache =
- new WeakReference[NUM_TYPES];
- private final WeakReference<OverflowMenuAdapter>[] mOverflowAdapterCache =
- new WeakReference[NUM_TYPES];
+ private ArrayList<MenuItemImpl> mTempShortcutItemList = new ArrayList<MenuItemImpl>();
- // Group IDs that have been added as actions - used temporarily, allocated here for reuse.
- private final SparseBooleanArray mActionButtonGroups = new SparseBooleanArray();
-
- private static int getAlertDialogTheme(Context context) {
- TypedValue outValue = new TypedValue();
- context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
- outValue, true);
- return outValue.resourceId;
- }
-
- private MenuType[] mMenuTypes;
- class MenuType {
- private int mMenuType;
-
- /** The layout inflater that uses the menu type's theme */
- private LayoutInflater mInflater;
-
- /** The lazily loaded {@link MenuView} */
- private WeakReference<MenuView> mMenuView;
-
- MenuType(int menuType) {
- mMenuType = menuType;
- }
-
- LayoutInflater getInflater() {
- // Create an inflater that uses the given theme for the Views it inflates
- if (mInflater == null) {
- Context wrappedContext;
- int themeResForType = THEME_RES_FOR_TYPE[mMenuType];
- switch (themeResForType) {
- case THEME_APPLICATION:
- wrappedContext = mContext;
- break;
- case THEME_ALERT_DIALOG:
- wrappedContext = new ContextThemeWrapper(mContext,
- getAlertDialogTheme(mContext));
- break;
- default:
- wrappedContext = new ContextThemeWrapper(mContext, themeResForType);
- break;
- }
- mInflater = (LayoutInflater) wrappedContext
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
-
- return mInflater;
- }
-
- MenuView getMenuView(ViewGroup parent) {
- if (LAYOUT_RES_FOR_TYPE[mMenuType] == 0) {
- return null;
- }
-
- synchronized (this) {
- MenuView menuView = mMenuView != null ? mMenuView.get() : null;
-
- if (menuView == null) {
- menuView = (MenuView) getInflater().inflate(
- LAYOUT_RES_FOR_TYPE[mMenuType], parent, false);
- menuView.initialize(MenuBuilder.this, mMenuType);
-
- // Cache the view
- mMenuView = new WeakReference<MenuView>(menuView);
-
- if (mFrozenViewStates != null) {
- View view = (View) menuView;
- view.restoreHierarchyState(mFrozenViewStates);
-
- // Clear this menu type's frozen state, since we just restored it
- mFrozenViewStates.remove(view.getId());
- }
- }
-
- return menuView;
- }
- }
-
- boolean hasMenuView() {
- return mMenuView != null && mMenuView.get() != null;
- }
- }
+ private CopyOnWriteArrayList<WeakReference<MenuPresenter>> mPresenters =
+ new CopyOnWriteArrayList<WeakReference<MenuPresenter>>();
/**
- * Called by menu to notify of close and selection changes
+ * Called by menu to notify of close and selection changes.
*/
public interface Callback {
/**
@@ -315,30 +160,6 @@
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item);
/**
- * Called when a menu is closed.
- * @param menu The menu that was closed.
- * @param allMenusAreClosing Whether the menus are completely closing (true),
- * or whether there is another menu opening shortly
- * (false). For example, if the menu is closing because a
- * sub menu is about to be shown, <var>allMenusAreClosing</var>
- * is false.
- */
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
-
- /**
- * Called when a sub menu is selected. This is a cue to open the given sub menu's decor.
- * @param subMenu the sub menu that is being opened
- * @return whether the sub menu selection was handled by the callback
- */
- public boolean onSubMenuSelected(SubMenuBuilder subMenu);
-
- /**
- * Called when a sub menu is closed
- * @param menu the sub menu that was closed
- */
- public void onCloseSubMenu(SubMenuBuilder menu);
-
- /**
* Called when the mode of the menu changes (for example, from icon to expanded).
*
* @param menu the menu that has changed modes
@@ -354,8 +175,6 @@
}
public MenuBuilder(Context context) {
- mMenuTypes = new MenuType[NUM_TYPES];
-
mContext = context;
mResources = context.getResources();
@@ -375,82 +194,66 @@
mDefaultShowAsAction = defaultShowAsAction;
return this;
}
-
- public void setCallback(Callback callback) {
- mCallback = callback;
+
+ /**
+ * Add a presenter to this menu. This will only hold a WeakReference;
+ * you do not need to explicitly remove a presenter, but you can using
+ * {@link #removeMenuPresenter(MenuPresenter)}.
+ *
+ * @param presenter The presenter to add
+ */
+ public void addMenuPresenter(MenuPresenter presenter) {
+ mPresenters.add(new WeakReference<MenuPresenter>(presenter));
+ presenter.initForMenu(mContext, this);
+ mIsActionItemsStale = true;
}
- MenuType getMenuType(int menuType) {
- if (mMenuTypes[menuType] == null) {
- mMenuTypes[menuType] = new MenuType(menuType);
- }
-
- return mMenuTypes[menuType];
- }
-
/**
- * Gets a menu View that contains this menu's items.
- *
- * @param menuType The type of menu to get a View for (must be one of
- * {@link #TYPE_ICON}, {@link #TYPE_EXPANDED},
- * {@link #TYPE_DIALOG}).
- * @param parent The ViewGroup that provides a set of LayoutParams values
- * for this menu view
- * @return A View for the menu of type <var>menuType</var>
+ * Remove a presenter from this menu. That presenter will no longer
+ * receive notifications of updates to this menu's data.
+ *
+ * @param presenter The presenter to remove
*/
- public View getMenuView(int menuType, ViewGroup parent) {
- // The expanded menu depends on the number if items shown in the icon menu (which
- // is adjustable as setters/XML attributes on IconMenuView [imagine a larger LCD
- // wanting to show more icons]). If, for example, the activity goes through
- // an orientation change while the expanded menu is open, the icon menu's view
- // won't have an instance anymore; so here we make sure we have an icon menu view (matching
- // the same parent so the layout parameters from the XML are used). This
- // will create the icon menu view and cache it (if it doesn't already exist).
- if (menuType == TYPE_EXPANDED
- && (mMenuTypes[TYPE_ICON] == null || !mMenuTypes[TYPE_ICON].hasMenuView())) {
- getMenuType(TYPE_ICON).getMenuView(parent);
+ public void removeMenuPresenter(MenuPresenter presenter) {
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter item = ref.get();
+ if (item == null || item == presenter) {
+ mPresenters.remove(ref);
+ }
}
-
- return (View) getMenuType(menuType).getMenuView(parent);
}
- private int getNumIconMenuItemsShown() {
- ViewGroup parent = null;
-
- if (!mMenuTypes[TYPE_ICON].hasMenuView()) {
- /*
- * There isn't an icon menu view instantiated, so when we get it
- * below, it will lazily instantiate it. We should pass a proper
- * parent so it uses the layout_ attributes present in the XML
- * layout file.
- */
- if (mMenuTypes[TYPE_EXPANDED].hasMenuView()) {
- View expandedMenuView = (View) mMenuTypes[TYPE_EXPANDED].getMenuView(null);
- parent = (ViewGroup) expandedMenuView.getParent();
+ private void dispatchPresenterUpdate(boolean cleared) {
+ if (mPresenters.isEmpty()) return;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.updateMenuView(cleared);
}
}
-
- return ((IconMenuView) getMenuView(TYPE_ICON, parent)).getNumActualItemsShown();
}
- /**
- * Clears the cached menu views. Call this if the menu views need to another
- * layout (for example, if the screen size has changed).
- */
- public void clearMenuViews() {
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (mMenuTypes[i] != null) {
- mMenuTypes[i].mMenuView = null;
+ private boolean dispatchSubMenuSelected(SubMenuBuilder subMenu) {
+ if (mPresenters.isEmpty()) return false;
+
+ boolean result = false;
+
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else if (!result) {
+ result = presenter.onSubMenuSelected(subMenu);
}
}
-
- for (int i = mItems.size() - 1; i >= 0; i--) {
- MenuItemImpl item = mItems.get(i);
- if (item.hasSubMenu()) {
- ((SubMenuBuilder) item.getSubMenu()).clearMenuViews();
- }
- item.clearItemViews();
- }
+ return result;
+ }
+
+ public void setCallback(Callback cb) {
+ mCallback = cb;
}
/**
@@ -468,7 +271,7 @@
}
mItems.add(findInsertIndex(mItems, ordering), item);
- onItemsChanged(false);
+ onItemsChanged(true);
return item;
}
@@ -554,7 +357,7 @@
}
// Notify menu views
- onItemsChanged(false);
+ onItemsChanged(true);
}
}
@@ -573,7 +376,7 @@
mItems.remove(index);
- if (updateChildrenOnMenuViews) onItemsChanged(false);
+ if (updateChildrenOnMenuViews) onItemsChanged(true);
}
public void removeItemAt(int index) {
@@ -585,6 +388,7 @@
clear();
clearHeader();
mPreventDispatchingItemsChanged = false;
+ mItemsChangedWhileDispatchPrevented = false;
onItemsChanged(true);
}
@@ -725,19 +529,14 @@
return mItems.get(index);
}
- public MenuItem getOverflowItem(int index) {
- flagActionItems(true);
- return mNonActionItems.get(index);
- }
-
public boolean isShortcutKey(int keyCode, KeyEvent event) {
return findItemWithShortcutForKey(keyCode, event) != null;
}
public void setQwertyMode(boolean isQwerty) {
mQwertyMode = isQwerty;
-
- refreshShortcuts(isShortcutsVisible(), isQwerty);
+
+ onItemsChanged(false);
}
/**
@@ -751,8 +550,7 @@
* @return An ordering integer that can be used to order this item across
* all the items (even from other categories).
*/
- private static int getOrdering(int categoryOrder)
- {
+ private static int getOrdering(int categoryOrder) {
final int index = (categoryOrder & CATEGORY_MASK) >> CATEGORY_SHIFT;
if (index < 0 || index >= sCategoryToOrder.length) {
@@ -770,23 +568,6 @@
}
/**
- * Refreshes the shortcut labels on each of the displayed items. Passes the arguments
- * so submenus don't need to call their parent menu for the same values.
- */
- private void refreshShortcuts(boolean shortcutsVisible, boolean qwertyMode) {
- MenuItemImpl item;
- for (int i = mItems.size() - 1; i >= 0; i--) {
- item = mItems.get(i);
-
- if (item.hasSubMenu()) {
- ((MenuBuilder) item.getSubMenu()).refreshShortcuts(shortcutsVisible, qwertyMode);
- }
-
- item.refreshShortcutOnItemViews(shortcutsVisible, qwertyMode);
- }
- }
-
- /**
* Sets whether the shortcuts should be visible on menus. Devices without hardware
* key input will never make shortcuts visible even if this method is passed 'true'.
*
@@ -798,7 +579,7 @@
if (mShortcutsVisible == shortcutsVisible) return;
setShortcutsVisibleInner(shortcutsVisible);
- refreshShortcuts(mShortcutsVisible, isQwertyMode());
+ onItemsChanged(false);
}
private void setShortcutsVisibleInner(boolean shortcutsVisible) {
@@ -818,15 +599,24 @@
Resources getResources() {
return mResources;
}
-
- public Callback getCallback() {
- return mCallback;
- }
public Context getContext() {
return mContext;
}
+ boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ return mCallback != null && mCallback.onMenuItemSelected(menu, item);
+ }
+
+ /**
+ * Dispatch a mode change event to this menu's callback.
+ */
+ public void changeMenuMode() {
+ if (mCallback != null) {
+ mCallback.onMenuModeChange(this);
+ }
+ }
+
private static int findInsertIndex(ArrayList<MenuItemImpl> items, int ordering) {
for (int i = items.size() - 1; i >= 0; i--) {
MenuItemImpl item = items.get(i);
@@ -860,7 +650,7 @@
* (the ALT-enabled char corresponds to the shortcut) associated
* with the keyCode.
*/
- List<MenuItemImpl> findItemsWithShortcutForKey(int keyCode, KeyEvent event) {
+ void findItemsWithShortcutForKey(List<MenuItemImpl> items, int keyCode, KeyEvent event) {
final boolean qwerty = isQwertyMode();
final int metaState = event.getMetaState();
final KeyCharacterMap.KeyData possibleChars = new KeyCharacterMap.KeyData();
@@ -868,18 +658,15 @@
final boolean isKeyCodeMapped = event.getKeyData(possibleChars);
// The delete key is not mapped to '\b' so we treat it specially
if (!isKeyCodeMapped && (keyCode != KeyEvent.KEYCODE_DEL)) {
- return null;
+ return;
}
- Vector<MenuItemImpl> items = new Vector();
// Look for an item whose shortcut is this key.
final int N = mItems.size();
for (int i = 0; i < N; i++) {
MenuItemImpl item = mItems.get(i);
if (item.hasSubMenu()) {
- List<MenuItemImpl> subMenuItems = ((MenuBuilder)item.getSubMenu())
- .findItemsWithShortcutForKey(keyCode, event);
- items.addAll(subMenuItems);
+ ((MenuBuilder)item.getSubMenu()).findItemsWithShortcutForKey(items, keyCode, event);
}
final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
if (((metaState & (KeyEvent.META_SHIFT_ON | KeyEvent.META_SYM_ON)) == 0) &&
@@ -892,7 +679,6 @@
items.add(item);
}
}
- return items;
}
/*
@@ -908,9 +694,11 @@
*/
MenuItemImpl findItemWithShortcutForKey(int keyCode, KeyEvent event) {
// Get all items that can be associated directly or indirectly with the keyCode
- List<MenuItemImpl> items = findItemsWithShortcutForKey(keyCode, event);
+ ArrayList<MenuItemImpl> items = mTempShortcutItemList;
+ items.clear();
+ findItemsWithShortcutForKey(items, keyCode, event);
- if (items == null) {
+ if (items.isEmpty()) {
return null;
}
@@ -920,15 +708,18 @@
event.getKeyData(possibleChars);
// If we have only one element, we can safely returns it
- if (items.size() == 1) {
+ final int size = items.size();
+ if (size == 1) {
return items.get(0);
}
final boolean qwerty = isQwertyMode();
// If we found more than one item associated with the key,
// we have to return the exact match
- for (MenuItemImpl item : items) {
- final char shortcutChar = qwerty ? item.getAlphabeticShortcut() : item.getNumericShortcut();
+ for (int i = 0; i < size; i++) {
+ final MenuItemImpl item = items.get(i);
+ final char shortcutChar = qwerty ? item.getAlphabeticShortcut() :
+ item.getNumericShortcut();
if ((shortcutChar == possibleChars.meta[0] &&
(metaState & KeyEvent.META_ALT_ON) == 0)
|| (shortcutChar == possibleChars.meta[2] &&
@@ -958,11 +749,8 @@
if (item.hasSubMenu()) {
close(false);
- if (mCallback != null) {
- // Return true if the sub menu was invoked or the item was invoked previously
- invoked = mCallback.onSubMenuSelected((SubMenuBuilder) item.getSubMenu())
- || invoked;
- }
+ invoked |= dispatchSubMenuSelected((SubMenuBuilder) item.getSubMenu());
+ if (!invoked) close(true);
} else {
if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
close(true);
@@ -982,10 +770,18 @@
* is false.
*/
final void close(boolean allMenusAreClosing) {
- Callback callback = getCallback();
- if (callback != null) {
- callback.onCloseMenu(this, allMenusAreClosing);
+ if (mIsClosing) return;
+
+ mIsClosing = true;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
+ } else {
+ presenter.onCloseMenu(this, allMenusAreClosing);
+ }
}
+ mIsClosing = false;
}
/** {@inheritDoc} */
@@ -996,26 +792,40 @@
/**
* Called when an item is added or removed.
*
- * @param cleared Whether the items were cleared or just changed.
+ * @param structureChanged true if the menu structure changed,
+ * false if only item properties changed.
*/
- private void onItemsChanged(boolean cleared) {
+ void onItemsChanged(boolean structureChanged) {
if (!mPreventDispatchingItemsChanged) {
- if (mIsVisibleItemsStale == false) mIsVisibleItemsStale = true;
- if (mIsActionItemsStale == false) mIsActionItemsStale = true;
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = 0; i < NUM_TYPES; i++) {
- if ((menuTypes[i] != null) && (menuTypes[i].hasMenuView())) {
- MenuView menuView = menuTypes[i].mMenuView.get();
- menuView.updateChildren(cleared);
- }
-
- MenuAdapter adapter = mAdapterCache[i] == null ? null : mAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
-
- adapter = mOverflowAdapterCache[i] == null ? null : mOverflowAdapterCache[i].get();
- if (adapter != null) adapter.notifyDataSetChanged();
+ if (structureChanged) {
+ mIsVisibleItemsStale = true;
+ mIsActionItemsStale = true;
}
+
+ dispatchPresenterUpdate(structureChanged);
+ } else {
+ mItemsChangedWhileDispatchPrevented = true;
+ }
+ }
+
+ /**
+ * Stop dispatching item changed events to presenters until
+ * {@link #startDispatchingItemsChanged()} is called. Useful when
+ * many menu operations are going to be performed as a batch.
+ */
+ public void stopDispatchingItemsChanged() {
+ if (!mPreventDispatchingItemsChanged) {
+ mPreventDispatchingItemsChanged = true;
+ mItemsChangedWhileDispatchPrevented = false;
+ }
+ }
+
+ public void startDispatchingItemsChanged() {
+ mPreventDispatchingItemsChanged = false;
+
+ if (mItemsChangedWhileDispatchPrevented) {
+ mItemsChangedWhileDispatchPrevented = false;
+ onItemsChanged(true);
}
}
@@ -1025,6 +835,7 @@
*/
void onItemVisibleChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsVisibleItemsStale = true;
onItemsChanged(false);
}
@@ -1034,6 +845,7 @@
*/
void onItemActionRequestChanged(MenuItemImpl item) {
// Notify of items being changed
+ mIsActionItemsStale = true;
onItemsChanged(false);
}
@@ -1055,17 +867,6 @@
return mVisibleItems;
}
-
- /**
- * @return A fake action button parent view for obtaining child views.
- */
- private ViewGroup getMeasureActionButtonParent() {
- if (mMeasureActionButtonParent == null) {
- mMeasureActionButtonParent = (ViewGroup) getMenuType(TYPE_ACTION_BUTTON).getInflater()
- .inflate(LAYOUT_RES_FOR_TYPE[TYPE_ACTION_BUTTON], null, false);
- }
- return mMeasureActionButtonParent;
- }
/**
* This method determines which menu items get to be 'action items' that will appear
@@ -1090,147 +891,56 @@
* <p>The space freed by demoting a full group cannot be consumed by future menu items.
* Once items begin to overflow, all future items become overflow items as well. This is
* to avoid inadvertent reordering that may break the app's intended design.
- *
- * @param reserveActionOverflow true if an overflow button should consume one space
- * in the available item count
*/
- private void flagActionItems(boolean reserveActionOverflow) {
- if (reserveActionOverflow != mReserveActionOverflow) {
- mReserveActionOverflow = reserveActionOverflow;
- mIsActionItemsStale = true;
- }
-
+ public void flagActionItems() {
if (!mIsActionItemsStale) {
return;
}
- final ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
- final int itemsSize = visibleItems.size();
- int maxActions = mMaxActionItems;
- int widthLimit = mActionWidthLimit;
- final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final ViewGroup parent = getMeasureActionButtonParent();
-
- int requiredItems = 0;
- int requestedItems = 0;
- int firstActionWidth = 0;
- boolean hasOverflow = false;
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.requiresActionButton()) {
- requiredItems++;
- } else if (item.requestsActionButton()) {
- requestedItems++;
+ // Presenters flag action items as needed.
+ boolean flagged = false;
+ for (WeakReference<MenuPresenter> ref : mPresenters) {
+ final MenuPresenter presenter = ref.get();
+ if (presenter == null) {
+ mPresenters.remove(ref);
} else {
- hasOverflow = true;
+ flagged |= presenter.flagActionItems();
}
}
- // Reserve a spot for the overflow item if needed.
- if (reserveActionOverflow &&
- (hasOverflow || requiredItems + requestedItems > maxActions)) {
- maxActions--;
- }
- maxActions -= requiredItems;
-
- final SparseBooleanArray seenGroups = mActionButtonGroups;
- seenGroups.clear();
-
- // Flag as many more requested items as will fit.
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
-
- if (item.requiresActionButton()) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
+ if (flagged) {
+ mActionItems.clear();
+ mNonActionItems.clear();
+ ArrayList<MenuItemImpl> visibleItems = getVisibleItems();
+ final int itemsSize = visibleItems.size();
+ for (int i = 0; i < itemsSize; i++) {
+ MenuItemImpl item = visibleItems.get(i);
+ if (item.isActionButton()) {
+ mActionItems.add(item);
+ } else {
+ mNonActionItems.add(item);
}
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
- }
- final int groupId = item.getGroupId();
- if (groupId != 0) {
- seenGroups.put(groupId, true);
- }
- } else if (item.requestsActionButton()) {
- // Items in a group with other items that already have an action slot
- // can break the max actions rule, but not the width limit.
- final int groupId = item.getGroupId();
- final boolean inGroup = seenGroups.get(groupId);
- boolean isAction = (maxActions > 0 || inGroup) && widthLimit > 0;
- maxActions--;
-
- if (isAction) {
- View v = item.getActionView();
- if (v == null) {
- v = item.getItemView(TYPE_ACTION_BUTTON, parent);
- }
- v.measure(querySpec, querySpec);
- final int measuredWidth = v.getMeasuredWidth();
- widthLimit -= measuredWidth;
- if (firstActionWidth == 0) {
- firstActionWidth = measuredWidth;
- }
-
- // Did this push the entire first item past halfway?
- if (widthLimit + firstActionWidth <= 0) {
- isAction = false;
- }
- }
-
- if (isAction && groupId != 0) {
- seenGroups.put(groupId, true);
- } else if (inGroup) {
- // We broke the width limit. Demote the whole group, they all overflow now.
- seenGroups.put(groupId, false);
- for (int j = 0; j < i; j++) {
- MenuItemImpl areYouMyGroupie = visibleItems.get(j);
- if (areYouMyGroupie.getGroupId() == groupId) {
- areYouMyGroupie.setIsActionButton(false);
- }
- }
- }
-
- item.setIsActionButton(isAction);
}
+ } else if (mActionItems.size() + mNonActionItems.size() != getVisibleItems().size()) {
+ // Nobody flagged anything, but if something doesn't add up then treat everything
+ // as non-action items.
+ // (This happens during a first pass with no action-item presenters.)
+ mActionItems.clear();
+ mNonActionItems.clear();
+ mNonActionItems.addAll(getVisibleItems());
}
-
- mActionItems.clear();
- mNonActionItems.clear();
- for (int i = 0; i < itemsSize; i++) {
- MenuItemImpl item = visibleItems.get(i);
- if (item.isActionButton()) {
- mActionItems.add(item);
- } else {
- mNonActionItems.add(item);
- }
- }
-
mIsActionItemsStale = false;
}
- ArrayList<MenuItemImpl> getActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getActionItems() {
+ flagActionItems();
return mActionItems;
}
- ArrayList<MenuItemImpl> getNonActionItems(boolean reserveActionOverflow) {
- flagActionItems(reserveActionOverflow);
+ ArrayList<MenuItemImpl> getNonActionItems() {
+ flagActionItems();
return mNonActionItems;
}
-
- void setMaxActionItems(int maxActionItems) {
- mMaxActionItems = maxActionItems;
- mIsActionItemsStale = true;
- }
-
- void setActionWidthLimit(int widthLimit) {
- mActionWidthLimit = widthLimit;
- mIsActionItemsStale = true;
- }
public void clearHeader() {
mHeaderIcon = null;
@@ -1362,38 +1072,6 @@
mCurrentMenuInfo = menuInfo;
}
- /**
- * Gets an adapter for providing items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getMenuAdapter(int menuType) {
- MenuAdapter adapter = mAdapterCache[menuType] == null ?
- null : mAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new MenuAdapter(menuType);
- mAdapterCache[menuType] = new WeakReference<MenuAdapter>(adapter);
- return adapter;
- }
-
- /**
- * Gets an adapter for providing overflow (non-action) items and their views.
- *
- * @param menuType The type of menu to get an adapter for.
- * @return A {@link MenuAdapter} for this menu with the given menu type.
- */
- public MenuAdapter getOverflowMenuAdapter(int menuType) {
- OverflowMenuAdapter adapter = mOverflowAdapterCache[menuType] == null ?
- null : mOverflowAdapterCache[menuType].get();
- if (adapter != null) return adapter;
-
- adapter = new OverflowMenuAdapter(menuType);
- mOverflowAdapterCache[menuType] = new WeakReference<OverflowMenuAdapter>(adapter);
- return adapter;
- }
-
void setOptionalIconsVisible(boolean visible) {
mOptionalIconsVisible = visible;
}
@@ -1401,109 +1079,4 @@
boolean getOptionalIconsVisible() {
return mOptionalIconsVisible;
}
-
- public void saveHierarchyState(Bundle outState) {
- SparseArray<Parcelable> viewStates = new SparseArray<Parcelable>();
-
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
- }
-
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).saveHierarchyState(viewStates);
- }
- }
-
- outState.putSparseParcelableArray(VIEWS_TAG, viewStates);
- }
-
- public void restoreHierarchyState(Bundle inState) {
- // Save this for menu views opened later
- SparseArray<Parcelable> viewStates = mFrozenViewStates = inState
- .getSparseParcelableArray(VIEWS_TAG);
-
- // Thaw those menu views already open
- MenuType[] menuTypes = mMenuTypes;
- for (int i = NUM_TYPES - 1; i >= 0; i--) {
- if (menuTypes[i] == null) {
- continue;
- }
-
- if (menuTypes[i].hasMenuView()) {
- ((View) menuTypes[i].getMenuView(null)).restoreHierarchyState(viewStates);
- }
- }
- }
-
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source. This adapter will use only the visible/shown items from the menu.
- */
- public class MenuAdapter extends BaseAdapter {
- private int mMenuType;
-
- public MenuAdapter(int menuType) {
- mMenuType = menuType;
- }
-
- public int getOffset() {
- if (mMenuType == TYPE_EXPANDED) {
- return getNumIconMenuItemsShown();
- } else {
- return 0;
- }
- }
-
- public int getCount() {
- return getVisibleItems().size() - getOffset();
- }
-
- public MenuItemImpl getItem(int position) {
- return getVisibleItems().get(position + getOffset());
- }
-
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView != null) {
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- itemView.getItemData().setItemView(mMenuType, null);
-
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- itemView.initialize(item, mMenuType);
- item.setItemView(mMenuType, itemView);
- return convertView;
- } else {
- MenuItemImpl item = (MenuItemImpl) getItem(position);
- item.setItemView(mMenuType, null);
- return item.getItemView(mMenuType, parent);
- }
- }
- }
-
- /**
- * An adapter that allows an {@link AdapterView} to use this {@link MenuBuilder} as a data
- * source for overflow menu items that do not fit in the list of action items.
- */
- private class OverflowMenuAdapter extends MenuAdapter {
- public OverflowMenuAdapter(int menuType) {
- super(menuType);
- }
-
- @Override
- public MenuItemImpl getItem(int position) {
- return getNonActionItems(true).get(position);
- }
-
- @Override
- public int getCount() {
- return getNonActionItems(true).size();
- }
- }
}
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index d7438d6..6387c9b 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -24,17 +24,19 @@
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
-import android.widget.ListAdapter;
/**
* Helper for menus that appear as Dialogs (context and submenus).
*
* @hide
*/
-public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogInterface.OnClickListener {
+public class MenuDialogHelper implements DialogInterface.OnKeyListener,
+ DialogInterface.OnClickListener,
+ DialogInterface.OnDismissListener,
+ MenuPresenter.Callback {
private MenuBuilder mMenu;
- private ListAdapter mAdapter;
private AlertDialog mDialog;
+ ListMenuPresenter mPresenter;
public MenuDialogHelper(MenuBuilder menu) {
mMenu = menu;
@@ -49,12 +51,15 @@
// Many references to mMenu, create local reference
final MenuBuilder menu = mMenu;
- // Get an adapter for the menu item views
- mAdapter = menu.getMenuAdapter(MenuBuilder.TYPE_DIALOG);
-
// Get the builder for the dialog
- final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext())
- .setAdapter(mAdapter, this);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(menu.getContext());
+
+ mPresenter = new ListMenuPresenter(builder.getContext(),
+ com.android.internal.R.layout.list_menu_item_layout);
+
+ mPresenter.setCallback(this);
+ mMenu.addMenuPresenter(mPresenter);
+ builder.setAdapter(mPresenter.getAdapter(), this);
// Set the title
final View headerView = menu.getHeaderView();
@@ -68,13 +73,10 @@
// Set the key listener
builder.setOnKeyListener(this);
-
- // Since this is for a menu, disable the recycling of views
- // This is done by the menu framework anyway
- builder.setRecycleOnMeasureEnabled(false);
// Show the menu
mDialog = builder.create();
+ mDialog.setOnDismissListener(this);
WindowManager.LayoutParams lp = mDialog.getWindow().getAttributes();
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
@@ -132,9 +134,25 @@
mDialog.dismiss();
}
}
-
- public void onClick(DialogInterface dialog, int which) {
- mMenu.performItemAction((MenuItemImpl) mAdapter.getItem(which), 0);
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mPresenter.onCloseMenu(mMenu, true);
}
-
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (allMenusAreClosing || menu == mMenu) {
+ dismiss();
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ return false;
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ mMenu.performItemAction((MenuItemImpl) mPresenter.getAdapter().getItem(which), 0);
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 305115f..42ef916 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,21 +16,18 @@
package com.android.internal.view.menu;
-import java.lang.ref.WeakReference;
+import com.android.internal.view.menu.MenuView.ItemView;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.util.Log;
+import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewDebug;
-import android.view.ViewGroup;
-import android.view.ContextMenu.ContextMenuInfo;
-
-import com.android.internal.view.menu.MenuView.ItemView;
/**
* @hide
@@ -60,9 +57,6 @@
* needed).
*/
private int mIconResId = NO_ICON;
-
- /** The (cached) menu item views for this item */
- private WeakReference<ItemView> mItemViews[];
/** The menu to which this item belongs */
private MenuBuilder mMenu;
@@ -128,7 +122,6 @@
com.android.internal.R.string.menu_space_shortcut_label);
}
- mItemViews = new WeakReference[MenuBuilder.NUM_TYPES];
mMenu = menu;
mId = id;
mGroup = group;
@@ -149,9 +142,7 @@
return true;
}
- MenuBuilder.Callback callback = mMenu.getCallback();
- if (callback != null &&
- callback.onMenuItemSelected(mMenu.getRootMenu(), this)) {
+ if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {
return true;
}
@@ -172,10 +163,6 @@
return false;
}
- private boolean hasItemView(int menuType) {
- return mItemViews[menuType] != null && mItemViews[menuType].get() != null;
- }
-
public boolean isEnabled() {
return (mFlags & ENABLED) != 0;
}
@@ -187,13 +174,7 @@
mFlags &= ~ENABLED;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (hasItemView(i)) {
- mItemViews[i].get().setEnabled(enabled);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
@@ -242,7 +223,7 @@
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -256,7 +237,7 @@
mShortcutNumericChar = numericChar;
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -265,7 +246,7 @@
mShortcutNumericChar = numericChar;
mShortcutAlphabeticChar = Character.toLowerCase(alphaChar);
- refreshShortcutOnItemViews();
+ mMenu.onItemsChanged(false);
return this;
}
@@ -322,38 +303,6 @@
return mMenu.isShortcutsVisible() && (getShortcut() != 0);
}
- /**
- * Refreshes the shortcut shown on the ItemViews. This method retrieves current
- * shortcut state (mode and shown) from the menu that contains this item.
- */
- private void refreshShortcutOnItemViews() {
- refreshShortcutOnItemViews(mMenu.isShortcutsVisible(), mMenu.isQwertyMode());
- }
-
- /**
- * Refreshes the shortcut shown on the ItemViews. This is usually called by
- * the {@link MenuBuilder} when it is refreshing the shortcuts on all item
- * views, so it passes arguments rather than each item calling a method on the menu to get
- * the same values.
- *
- * @param menuShortcutShown The menu's shortcut shown mode. In addition,
- * this method will ensure this item has a shortcut before it
- * displays the shortcut.
- * @param isQwertyMode Whether the shortcut mode is qwerty mode
- */
- void refreshShortcutOnItemViews(boolean menuShortcutShown, boolean isQwertyMode) {
- final char shortcutKey = (isQwertyMode) ? mShortcutAlphabeticChar : mShortcutNumericChar;
-
- // Show shortcuts if the menu is supposed to show shortcuts AND this item has a shortcut
- final boolean showShortcut = menuShortcutShown && (shortcutKey != 0);
-
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setShortcut(showShortcut, shortcutKey);
- }
- }
- }
-
public SubMenu getSubMenu() {
return mSubMenu;
}
@@ -394,18 +343,7 @@
public MenuItem setTitle(CharSequence title) {
mTitle = title;
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // If the item view prefers a condensed title, only set this title if there
- // is no condensed title for this item
- if (!hasItemView(i)) {
- continue;
- }
-
- ItemView itemView = mItemViews[i].get();
- if (!itemView.prefersCondensedTitle() || mTitleCondensed == null) {
- itemView.setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
if (mSubMenu != null) {
mSubMenu.setHeaderTitle(title);
@@ -430,18 +368,12 @@
title = mTitle;
}
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that prefer a condensed title
- if (hasItemView(i) && (mItemViews[i].get().prefersCondensedTitle())) {
- mItemViews[i].get().setTitle(title);
- }
- }
+ mMenu.onItemsChanged(false);
return this;
}
public Drawable getIcon() {
-
if (mIconDrawable != null) {
return mIconDrawable;
}
@@ -456,7 +388,7 @@
public MenuItem setIcon(Drawable icon) {
mIconResId = NO_ICON;
mIconDrawable = icon;
- setIconOnViews(icon);
+ mMenu.onItemsChanged(false);
return this;
}
@@ -466,33 +398,10 @@
mIconResId = iconResId;
// If we have a view, we need to push the Drawable to them
- if (haveAnyOpenedIconCapableItemViews()) {
- Drawable drawable = iconResId != NO_ICON ? mMenu.getResources().getDrawable(iconResId)
- : null;
- setIconOnViews(drawable);
- }
+ mMenu.onItemsChanged(false);
return this;
}
-
- private void setIconOnViews(Drawable icon) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- // Refresh those item views that are able to display an icon
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- mItemViews[i].get().setIcon(icon);
- }
- }
- }
-
- private boolean haveAnyOpenedIconCapableItemViews() {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i) && mItemViews[i].get().showsIcon()) {
- return true;
- }
- }
-
- return false;
- }
public boolean isCheckable() {
return (mFlags & CHECKABLE) == CHECKABLE;
@@ -502,19 +411,14 @@
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKABLE) | (checkable ? CHECKABLE : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setCheckable(checkable);
- }
- }
+ mMenu.onItemsChanged(false);
}
return this;
}
- public void setExclusiveCheckable(boolean exclusive)
- {
- mFlags = (mFlags&~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
+ public void setExclusiveCheckable(boolean exclusive) {
+ mFlags = (mFlags & ~EXCLUSIVE) | (exclusive ? EXCLUSIVE : 0);
}
public boolean isExclusiveCheckable() {
@@ -541,11 +445,7 @@
final int oldFlags = mFlags;
mFlags = (mFlags & ~CHECKED) | (checked ? CHECKED : 0);
if (oldFlags != mFlags) {
- for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) {
- if (hasItemView(i)) {
- mItemViews[i].get().setChecked(checked);
- }
- }
+ mMenu.onItemsChanged(false);
}
}
@@ -581,39 +481,6 @@
mClickListener = clickListener;
return this;
}
-
- View getItemView(int menuType, ViewGroup parent) {
- if (!hasItemView(menuType)) {
- mItemViews[menuType] = new WeakReference<ItemView>(createItemView(menuType, parent));
- }
-
- return (View) mItemViews[menuType].get();
- }
-
- void setItemView(int menuType, ItemView view) {
- mItemViews[menuType] = new WeakReference<ItemView>(view);
- }
-
- /**
- * Create and initializes a menu item view that implements {@link MenuView.ItemView}.
- * @param menuType The type of menu to get a View for (must be one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_SUB}, {@link MenuBuilder#TYPE_CONTEXT}).
- * @return The inflated {@link MenuView.ItemView} that is ready for use
- */
- private MenuView.ItemView createItemView(int menuType, ViewGroup parent) {
- // Create the MenuView
- MenuView.ItemView itemView = (MenuView.ItemView) getLayoutInflater(menuType)
- .inflate(MenuBuilder.ITEM_LAYOUT_RES_FOR_TYPE[menuType], parent, false);
- itemView.initialize(this, menuType);
- return itemView;
- }
-
- void clearItemViews() {
- for (int i = mItemViews.length - 1; i >= 0; i--) {
- mItemViews[i] = null;
- }
- }
@Override
public String toString() {
@@ -627,24 +494,12 @@
public ContextMenuInfo getMenuInfo() {
return mMenuInfo;
}
-
- /**
- * Returns a LayoutInflater that is themed for the given menu type.
- *
- * @param menuType The type of menu.
- * @return A LayoutInflater.
- */
- public LayoutInflater getLayoutInflater(int menuType) {
- return mMenu.getMenuType(menuType).getInflater();
- }
/**
- * @return Whether the given menu type should show icons for menu items.
+ * @return Whether the menu should show icons for menu items.
*/
- public boolean shouldShowIcon(int menuType) {
- return menuType == MenuBuilder.TYPE_ICON ||
- menuType == MenuBuilder.TYPE_ACTION_BUTTON ||
- mMenu.getOptionalIconsVisible();
+ public boolean shouldShowIcon() {
+ return mMenu.getOptionalIconsVisible();
}
public boolean isActionButton() {
@@ -696,8 +551,8 @@
public MenuItem setActionView(int resId) {
LayoutInflater inflater = LayoutInflater.from(mMenu.getContext());
- ViewGroup parent = (ViewGroup) mMenu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- setActionView(inflater.inflate(resId, parent, false));
+ // TODO - Fix for proper parent. Lazily inflate in the presenter.
+ setActionView(inflater.inflate(resId, null));
return this;
}
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 04a059e..38cec29 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -16,31 +16,35 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
import android.content.Context;
-import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
-import android.view.MenuItem;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListAdapter;
import android.widget.ListPopupWindow;
import android.widget.PopupWindow;
-import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
+ * Presents a menu as a small, simple popup anchored to another view.
* @hide
*/
public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
- View.OnAttachStateChangeListener {
+ View.OnAttachStateChangeListener, MenuPresenter {
private static final String TAG = "MenuPopupHelper";
+ static final int ITEM_LAYOUT = com.android.internal.R.layout.popup_menu_item_layout;
+
private Context mContext;
+ private LayoutInflater mInflater;
private ListPopupWindow mPopup;
private MenuBuilder mMenu;
private int mPopupMaxWidth;
@@ -48,7 +52,9 @@
private boolean mOverflowOnly;
private ViewTreeObserver mTreeObserver;
- private final Handler mHandler = new Handler();
+ private MenuAdapter mAdapter;
+
+ private Callback mPresenterCallback;
public MenuPopupHelper(Context context, MenuBuilder menu) {
this(context, menu, null, false);
@@ -61,6 +67,7 @@
public MenuPopupHelper(Context context, MenuBuilder menu,
View anchorView, boolean overflowOnly) {
mContext = context;
+ mInflater = LayoutInflater.from(context);
mMenu = menu;
mOverflowOnly = overflowOnly;
@@ -68,6 +75,8 @@
mPopupMaxWidth = metrics.widthPixels / 2;
mAnchorView = anchorView;
+
+ menu.addMenuPresenter(this);
}
public void setAnchorView(View anchor) {
@@ -82,23 +91,14 @@
public boolean tryShow() {
mPopup = new ListPopupWindow(mContext, null, com.android.internal.R.attr.popupMenuStyle);
- mPopup.setOnItemClickListener(this);
mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
- final MenuAdapter adapter = mOverflowOnly ?
- mMenu.getOverflowMenuAdapter(MenuBuilder.TYPE_POPUP) :
- mMenu.getMenuAdapter(MenuBuilder.TYPE_POPUP);
- mPopup.setAdapter(adapter);
+ mAdapter = new MenuAdapter(mMenu);
+ mPopup.setAdapter(mAdapter);
mPopup.setModal(true);
View anchor = mAnchorView;
- if (anchor == null && mMenu instanceof SubMenuBuilder) {
- SubMenuBuilder subMenu = (SubMenuBuilder) mMenu;
- final MenuItemImpl itemImpl = (MenuItemImpl) subMenu.getItem();
- anchor = itemImpl.getItemView(MenuBuilder.TYPE_ACTION_BUTTON, null);
- mAnchorView = anchor;
- }
-
if (anchor != null) {
final boolean addGlobalListener = mTreeObserver == null;
mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
@@ -109,7 +109,7 @@
return false;
}
- mPopup.setContentWidth(Math.min(measureContentWidth(adapter), mPopupMaxWidth));
+ mPopup.setContentWidth(Math.min(measureContentWidth(mAdapter), mPopupMaxWidth));
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
mPopup.show();
mPopup.getListView().setOnKeyListener(this);
@@ -136,23 +136,10 @@
return mPopup != null && mPopup.isShowing();
}
+ @Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (!isShowing()) return;
-
- MenuItem item = null;
- if (mOverflowOnly) {
- item = mMenu.getOverflowItem(position);
- } else {
- item = mMenu.getVisibleItems().get(position);
- }
- dismiss();
-
- final MenuItem performItem = item;
- mHandler.post(new Runnable() {
- public void run() {
- mMenu.performItemAction(performItem, 0);
- }
- });
+ MenuAdapter adapter = mAdapter;
+ adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
}
public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -163,7 +150,7 @@
return false;
}
- private int measureContentWidth(MenuAdapter adapter) {
+ private int measureContentWidth(ListAdapter adapter) {
// Menus don't tend to be long, so this is more sane than it looks.
int width = 0;
View itemView = null;
@@ -211,4 +198,91 @@
}
v.removeOnAttachStateChangeListener(this);
}
+
+ @Override
+ public void initForMenu(Context context, MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ if (mAdapter != null) mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView, false);
+ subPopup.setCallback(mPresenterCallback);
+ if (subPopup.tryShow()) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ private class MenuAdapter extends BaseAdapter {
+ private MenuBuilder mAdapterMenu;
+
+ public MenuAdapter(MenuBuilder menu) {
+ mAdapterMenu = menu;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.size();
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
new file mode 100644
index 0000000..5baf419
--- /dev/null
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -0,0 +1,110 @@
+/*
+ * 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.internal.view.menu;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.ViewGroup;
+
+/**
+ * A MenuPresenter is responsible for building views for a Menu object.
+ * It takes over some responsibility from the old style monolithic MenuBuilder class.
+ */
+public interface MenuPresenter {
+ /**
+ * Called by menu implementation to notify another component of open/close events.
+ */
+ public interface Callback {
+ /**
+ * Called when a menu is closing.
+ * @param menu
+ * @param allMenusAreClosing
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called when a submenu opens. Useful for notifying the application
+ * of menu state so that it does not attempt to hide the action bar
+ * while a submenu is open or similar.
+ *
+ * @param subMenu Submenu currently being opened
+ * @return true if the Callback will handle presenting the submenu, false if
+ * the presenter should attempt to do so.
+ */
+ public boolean onOpenSubMenu(MenuBuilder subMenu);
+ }
+
+ /**
+ * Initialize this presenter for the given context and menu.
+ * This method is called by MenuBuilder when a presenter is
+ * added. See {@link MenuBuilder#addMenuPresenter(MenuPresenter)}
+ *
+ * @param context Context for this presenter; used for view creation and resource management
+ * @param menu Menu to host
+ */
+ public void initForMenu(Context context, MenuBuilder menu);
+
+ /**
+ * Retrieve a MenuView to display the menu specified in
+ * {@link #initForMenu(Context, Menu)}.
+ *
+ * @param root Intended parent of the MenuView.
+ * @return A freshly created MenuView.
+ */
+ public MenuView getMenuView(ViewGroup root);
+
+ /**
+ * Update the menu UI in response to a change. Called by
+ * MenuBuilder during the normal course of operation.
+ *
+ * @param cleared true if the menu was entirely cleared
+ */
+ public void updateMenuView(boolean cleared);
+
+ /**
+ * Set a callback object that will be notified of menu events
+ * related to this specific presentation.
+ * @param cb Callback that will be notified of future events
+ */
+ public void setCallback(Callback cb);
+
+ /**
+ * Called by Menu implementations to indicate that a submenu item
+ * has been selected. An active Callback should be notified, and
+ * if applicable the presenter should present the submenu.
+ *
+ * @param subMenu SubMenu being opened
+ * @return true if the the event was handled, false otherwise.
+ */
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu);
+
+ /**
+ * Called by Menu implementations to indicate that a menu or submenu is
+ * closing. Presenter implementations should close the representation
+ * of the menu indicated as necessary and notify a registered callback.
+ *
+ * @param menu Menu or submenu that is closing.
+ * @param allMenusAreClosing True if all associated menus are closing.
+ */
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing);
+
+ /**
+ * Called by Menu implementations to flag items that will be shown as actions.
+ * @return true if this presenter changed the action status of any items.
+ */
+ public boolean flagActionItems();
+}
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 5090400..407caae 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -22,7 +22,7 @@
import android.graphics.drawable.Drawable;
/**
- * Minimal interface for a menu view. {@link #initialize(MenuBuilder, int)} must be called for the
+ * Minimal interface for a menu view. {@link #initialize(MenuBuilder)} must be called for the
* menu to be functional.
*
* @hide
@@ -33,18 +33,8 @@
* view is inflated.
*
* @param menu The menu that this MenuView should display.
- * @param menuType The type of this menu, one of
- * {@link MenuBuilder#TYPE_ICON}, {@link MenuBuilder#TYPE_EXPANDED},
- * {@link MenuBuilder#TYPE_DIALOG}).
*/
- public void initialize(MenuBuilder menu, int menuType);
-
- /**
- * Forces the menu view to update its view to reflect the new state of the menu.
- *
- * @param cleared Whether the menu was cleared or just modified.
- */
- public void updateChildren(boolean cleared);
+ public void initialize(MenuBuilder menu);
/**
* Returns the default animations to be used for this menu when entering/exiting.
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index af1b996..ad773ee 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -67,11 +67,6 @@
}
@Override
- public Callback getCallback() {
- return mParentMenu.getCallback();
- }
-
- @Override
public void setCallback(Callback callback) {
mParentMenu.setCallback(callback);
}
@@ -110,5 +105,4 @@
public SubMenu setHeaderView(View view) {
return (SubMenu) super.setHeaderViewInt(view);
}
-
}
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 71af115..70fb3b2 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -16,6 +16,7 @@
package com.android.internal.widget;
import com.android.internal.R;
+import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
@@ -53,6 +54,7 @@
private int mTitleStyleRes;
private int mSubtitleStyleRes;
private ActionMenuView mMenuView;
+ private ActionMenuPresenter mPresenter;
private Animator mCurrentAnimation;
private boolean mAnimateInOnLayout;
@@ -176,9 +178,9 @@
});
final MenuBuilder menu = (MenuBuilder) mode.getMenu();
- mMenuView = (ActionMenuView) menu.getMenuView(MenuBuilder.TYPE_ACTION_BUTTON, this);
- mMenuView.setOverflowReserved(true);
- mMenuView.updateChildren(false);
+ mPresenter = new ActionMenuPresenter();
+ menu.addMenuPresenter(mPresenter);
+ mMenuView = (ActionMenuView) mPresenter.getMenuView(this);
addView(mMenuView);
mAnimateInOnLayout = true;
@@ -217,28 +219,22 @@
}
public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
+ if (mPresenter != null) {
+ return mPresenter.showOverflowMenu();
}
return false;
}
- public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
- }
- }
-
public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
+ if (mPresenter != null) {
+ return mPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
+ if (mPresenter != null) {
+ return mPresenter.isOverflowMenuShowing();
}
return false;
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index 2d9a9f2..74a6ae7 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -18,8 +18,10 @@
import com.android.internal.R;
import com.android.internal.view.menu.ActionMenuItem;
+import com.android.internal.view.menu.ActionMenuPresenter;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuPresenter;
import android.app.ActionBar;
import android.app.ActionBar.OnNavigationListener;
@@ -112,6 +114,7 @@
private MenuBuilder mOptionsMenu;
private ActionMenuView mMenuView;
+ private ActionMenuPresenter mActionMenuPresenter;
private ActionBarContextView mContextView;
@@ -250,16 +253,24 @@
mCallback = callback;
}
- public void setMenu(Menu menu) {
+ public void setMenu(Menu menu, MenuPresenter.Callback cb) {
if (menu == mOptionsMenu) return;
+ if (mOptionsMenu != null) {
+ mOptionsMenu.removeMenuPresenter(mActionMenuPresenter);
+ }
+
MenuBuilder builder = (MenuBuilder) menu;
mOptionsMenu = builder;
if (mMenuView != null) {
removeView(mMenuView);
}
- final ActionMenuView menuView = (ActionMenuView) builder.getMenuView(
- MenuBuilder.TYPE_ACTION_BUTTON, null);
+ if (mActionMenuPresenter == null) {
+ mActionMenuPresenter = new ActionMenuPresenter();
+ mActionMenuPresenter.setCallback(cb);
+ builder.addMenuPresenter(mActionMenuPresenter);
+ }
+ final ActionMenuView menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this);
final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.MATCH_PARENT);
menuView.setLayoutParams(layoutParams);
@@ -268,15 +279,15 @@
}
public boolean showOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.showOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.showOverflowMenu();
}
return false;
}
public void openOverflowMenu() {
- if (mMenuView != null) {
- mMenuView.openOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ showOverflowMenu();
}
}
@@ -289,28 +300,27 @@
}
public boolean hideOverflowMenu() {
- if (mMenuView != null) {
- return mMenuView.hideOverflowMenu();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.hideOverflowMenu();
}
return false;
}
public boolean isOverflowMenuShowing() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuShowing();
- }
- return false;
- }
-
- public boolean isOverflowMenuOpen() {
- if (mMenuView != null) {
- return mMenuView.isOverflowMenuOpen();
+ if (mActionMenuPresenter != null) {
+ return mActionMenuPresenter.isOverflowMenuShowing();
}
return false;
}
public boolean isOverflowReserved() {
- return mMenuView != null && mMenuView.isOverflowReserved();
+ return mActionMenuPresenter != null && mActionMenuPresenter.isOverflowReserved();
+ }
+
+ public void dismissPopupMenus() {
+ if (mActionMenuPresenter != null) {
+ mActionMenuPresenter.dismissPopupMenus();
+ }
}
public void setCustomNavigationView(View view) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index f8f8761..290f528 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -54,6 +54,7 @@
android_view_KeyCharacterMap.cpp \
android_view_GLES20Canvas.cpp \
android_view_MotionEvent.cpp \
+ android_view_PointerIcon.cpp \
android_view_VelocityTracker.cpp \
android_text_AndroidCharacter.cpp \
android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b628b9dc..a4a229a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -170,6 +170,7 @@
extern int register_android_view_InputQueue(JNIEnv* env);
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
+extern int register_android_view_PointerIcon(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
@@ -1212,6 +1213,7 @@
REG_JNI(register_android_view_InputQueue),
REG_JNI(register_android_view_KeyEvent),
REG_JNI(register_android_view_MotionEvent),
+ REG_JNI(register_android_view_PointerIcon),
REG_JNI(register_android_view_VelocityTracker),
REG_JNI(register_android_content_res_ObbScanner),
diff --git a/core/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp
index 207c72f..310f02f 100644
--- a/core/jni/android/graphics/Canvas.cpp
+++ b/core/jni/android/graphics/Canvas.cpp
@@ -743,7 +743,11 @@
jcharArray text, int index, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
jchar* textArray = env->GetCharArrayElements(text, NULL);
+#if RTL_USE_HARFBUZZ
+ drawTextWithGlyphs(canvas, textArray + index, 0, count, x, y, flags, paint);
+#else
TextLayout::drawText(paint, textArray + index, count, flags, x, y, canvas);
+#endif
env->ReleaseCharArrayElements(text, textArray, JNI_ABORT);
}
@@ -752,7 +756,11 @@
int start, int end,
jfloat x, jfloat y, int flags, SkPaint* paint) {
const jchar* textArray = env->GetStringChars(text, NULL);
+#if RTL_USE_HARFBUZZ
+ drawTextWithGlyphs(canvas, textArray, start, end, x, y, flags, paint);
+#else
TextLayout::drawText(paint, textArray + start, end - start, flags, x, y, canvas);
+#endif
env->ReleaseStringChars(text, textArray);
}
@@ -781,6 +789,23 @@
x, y, flags, paint);
}
+ static void drawTextWithGlyphs(SkCanvas* canvas, const jchar* textArray,
+ int start, int count, int contextCount,
+ jfloat x, jfloat y, int flags, SkPaint* paint) {
+
+ sp<TextLayoutCacheValue> value = gTextLayoutCache.getValue(
+ paint, textArray, start, count, contextCount, flags);
+ if (value == NULL) {
+ LOGE("drawTextWithGlyphs -- cannot get Cache value");
+ return ;
+ }
+#if DEBUG_GLYPHS
+ logGlyphs(value);
+#endif
+ doDrawGlyphs(canvas, value->getGlyphs(), 0, value->getGlyphsCount(),
+ x, y, flags, paint);
+ }
+
static void drawTextWithGlyphs___CIIFFIPaint(JNIEnv* env, jobject, SkCanvas* canvas,
jcharArray text, int index, int count,
jfloat x, jfloat y, int flags, SkPaint* paint) {
@@ -831,8 +856,13 @@
jfloat x, jfloat y, int dirFlags, SkPaint* paint) {
jchar* chars = env->GetCharArrayElements(text, NULL);
+#if RTL_USE_HARFBUZZ
+ drawTextWithGlyphs(canvas, chars + contextIndex, index - contextIndex,
+ count, contextCount, x, y, dirFlags, paint);
+#else
TextLayout::drawTextRun(paint, chars + contextIndex, index - contextIndex,
- count, contextCount, dirFlags, x, y, canvas);
+ count, contextCount, dirFlags, x, y, canvas);
+#endif
env->ReleaseCharArrayElements(text, chars, JNI_ABORT);
}
@@ -844,8 +874,13 @@
jint count = end - start;
jint contextCount = contextEnd - contextStart;
const jchar* chars = env->GetStringChars(text, NULL);
+#if RTL_USE_HARFBUZZ
+ drawTextWithGlyphs(canvas, chars + contextStart, start - contextStart,
+ count, contextCount, x, y, dirFlags, paint);
+#else
TextLayout::drawTextRun(paint, chars + contextStart, start - contextStart,
- count, contextCount, dirFlags, x, y, canvas);
+ count, contextCount, dirFlags, x, y, canvas);
+#endif
env->ReleaseStringChars(text, chars);
}
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 04c7fb9..0a54e17 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -331,14 +331,19 @@
return 0;
}
- const SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
+ SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
const jchar* textArray = env->GetCharArrayElements(text, NULL);
+ jfloat result = 0;
+#if RTL_USE_HARFBUZZ
+ TextLayout::getTextRunAdvances(paint, textArray, index, count, count, paint->getFlags(),
+ NULL /* dont need all advances */, result);
+#else
// we double count, since measureText wants a byteLength
SkScalar width = paint->measureText(textArray + index, count << 1);
- env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray),
- JNI_ABORT);
-
- return SkScalarToFloat(width);
+ result = SkScalarToFloat(width);
+#endif
+ env->ReleaseCharArrayElements(text, const_cast<jchar*>(textArray), JNI_ABORT);
+ return result;
}
static jfloat measureText_StringII(JNIEnv* env, jobject jpaint, jstring text, int start, int end) {
@@ -347,15 +352,22 @@
SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
const jchar* textArray = env->GetStringChars(text, NULL);
- size_t textLength = env->GetStringLength(text);
int count = end - start;
+ size_t textLength = env->GetStringLength(text);
if ((start | count) < 0 || (size_t)count > textLength) {
doThrowAIOOBE(env);
return 0;
}
+ jfloat width = 0;
- jfloat width = SkScalarToFloat(paint->measureText(textArray + start, count << 1));
+#if RTL_USE_HARFBUZZ
+ TextLayout::getTextRunAdvances(paint, textArray, 0, count, count, paint->getFlags(),
+ NULL /* dont need all advances */, width);
+#else
+
+ width = SkScalarToFloat(paint->measureText(textArray + start, count << 1));
+#endif
env->ReleaseStringChars(text, textArray);
return width;
}
@@ -367,8 +379,13 @@
SkPaint* paint = GraphicsJNI::getNativePaint(env, jpaint);
const jchar* textArray = env->GetStringChars(text, NULL);
size_t textLength = env->GetStringLength(text);
-
- jfloat width = SkScalarToFloat(paint->measureText(textArray, textLength << 1));
+ jfloat width = 0;
+#if RTL_USE_HARFBUZZ
+ TextLayout::getTextRunAdvances(paint, textArray, 0, textLength, textLength, paint->getFlags(),
+ NULL /* dont need all advances */, width);
+#else
+ width = SkScalarToFloat(paint->measureText(textArray, textLength << 1));
+#endif
env->ReleaseStringChars(text, textArray);
return width;
}
@@ -376,12 +393,19 @@
static int dotextwidths(JNIEnv* env, SkPaint* paint, const jchar text[], int count, jfloatArray widths) {
AutoJavaFloatArray autoWidths(env, widths, count);
jfloat* widthsArray = autoWidths.ptr();
+#if RTL_USE_HARFBUZZ
+ jfloat totalAdvance;
+
+ TextLayout::getTextRunAdvances(paint, text, 0, count, count, paint->getFlags(),
+ widthsArray, totalAdvance);
+#else
SkScalar* scalarArray = (SkScalar*)widthsArray;
count = paint->getTextWidths(text, count << 1, scalarArray);
for (int i = 0; i < count; i++) {
widthsArray[i] = SkScalarToFloat(scalarArray[i]);
}
+#endif
return count;
}
@@ -502,6 +526,13 @@
static jint doTextRunCursor(JNIEnv *env, SkPaint* paint, const jchar *text, jint start,
jint count, jint flags, jint offset, jint opt) {
+#if RTL_USE_HARFBUZZ
+ jfloat scalarArray[count];
+ jfloat totalAdvance;
+
+ TextLayout::getTextRunAdvances(paint, text, start, count, count, flags,
+ scalarArray, totalAdvance);
+#else
SkScalar scalarArray[count];
jchar buffer[count];
@@ -543,7 +574,7 @@
}
}
}
-
+#endif
jint pos = offset - start;
switch (opt) {
case AFTER:
diff --git a/core/jni/android/graphics/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h
index 4fac89a..a41c91b 100644
--- a/core/jni/android/graphics/RtlProperties.h
+++ b/core/jni/android/graphics/RtlProperties.h
@@ -52,7 +52,7 @@
#define DEBUG_ADVANCES 0
// Define if we want (1) to have Glyphs debug values or not (0)
-#define DEBUG_GLYPHS 1
+#define DEBUG_GLYPHS 0
} // namespace android
#endif // ANDROID_RTL_PROPERTIES_H
diff --git a/core/jni/android/graphics/TextLayout.cpp b/core/jni/android/graphics/TextLayout.cpp
index 2890662..46e6c2b 100644
--- a/core/jni/android/graphics/TextLayout.cpp
+++ b/core/jni/android/graphics/TextLayout.cpp
@@ -259,7 +259,9 @@
sp<TextLayoutCacheValue> layout = gTextLayoutCache.getValue(
paint, chars, start, count, contextCount, dirFlags);
if (layout != NULL) {
- memcpy(resultAdvances, layout->getAdvances(), layout->getAdvancesCount() * sizeof(jfloat));
+ if (resultAdvances != NULL) {
+ memcpy(resultAdvances, layout->getAdvances(), layout->getAdvancesCount() * sizeof(jfloat));
+ }
resultTotalAdvance = layout->getTotalAdvance();
}
#else
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 088202e..77a731a 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -420,7 +420,9 @@
UBiDi* bidi = ubidi_open();
if (bidi) {
UErrorCode status = U_ZERO_ERROR;
+#if DEBUG_GLYPHS
LOGD("computeValuesWithHarfbuzz -- bidiReq=%d", bidiReq);
+#endif
ubidi_setPara(bidi, chars, contextCount, bidiReq, NULL, &status);
if (U_SUCCESS(status)) {
int paraDir = ubidi_getParaLevel(bidi) & kDirection_Mask; // 0 if ltr, 1 if rtl
@@ -430,7 +432,6 @@
#endif
if (rc == 1 || !U_SUCCESS(status)) {
- LOGD("HERE !!!");
computeRunValuesWithHarfbuzz(paint, chars, start, count, contextCount,
dirFlags, outAdvances, outTotalAdvance, outGlyphs, outGlyphsCount);
ubidi_close(bidi);
@@ -517,16 +518,25 @@
#endif
// Get Advances and their total
- jfloat totalAdvance = 0;
- for (size_t i = 0; i < count; i++) {
- totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[i]);
-#if DEBUG_ADVANCES
- LOGD("hb-adv = %d - rebased = %f - total = %f", shaperItem.advances[i], outAdvances[i],
- totalAdvance);
-#endif
+ jfloat totalAdvance = outAdvances[0] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[0]]);
+ for (size_t i = 1; i < count; i++) {
+ size_t clusterPrevious = shaperItem.log_clusters[i - 1];
+ size_t cluster = shaperItem.log_clusters[i];
+ if (cluster == clusterPrevious) {
+ outAdvances[i] = 0;
+ } else {
+ totalAdvance += outAdvances[i] = HBFixedToFloat(shaperItem.advances[shaperItem.log_clusters[i]]);
+ }
}
*outTotalAdvance = totalAdvance;
+#if DEBUG_ADVANCES
+ for (size_t i = 0; i < count; i++) {
+ LOGD("hb-adv[%d] = %f - log_clusters = %d - total = %f", i,
+ outAdvances[i], shaperItem.log_clusters[i], totalAdvance);
+ }
+#endif
+
// Get Glyphs
if (outGlyphs) {
*outGlyphsCount = shaperItem.num_glyphs;
diff --git a/core/jni/android_view_PointerIcon.cpp b/core/jni/android_view_PointerIcon.cpp
new file mode 100644
index 0000000..091341a
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.cpp
@@ -0,0 +1,149 @@
+/*
+ * 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_TAG "PointerIcon-JNI"
+
+#include "JNIHelp.h"
+
+#include "android_view_PointerIcon.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <utils/Log.h>
+#include <android/graphics/GraphicsJNI.h>
+
+namespace android {
+
+static struct {
+ jclass clazz;
+ jfieldID mStyle;
+ jfieldID mBitmap;
+ jfieldID mHotSpotX;
+ jfieldID mHotSpotY;
+ jmethodID getSystemIcon;
+ jmethodID load;
+} gPointerIconClassInfo;
+
+
+// --- Global Functions ---
+
+jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env, jobject contextObj, int32_t style) {
+ jobject pointerIconObj = env->CallStaticObjectMethod(gPointerIconClassInfo.clazz,
+ gPointerIconClassInfo.getSystemIcon, contextObj, style);
+ if (env->ExceptionCheck()) {
+ LOGW("An exception occurred while getting a pointer icon with style %d.", style);
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return NULL;
+ }
+ return pointerIconObj;
+}
+
+status_t android_view_PointerIcon_load(JNIEnv* env, jobject pointerIconObj, jobject contextObj,
+ PointerIcon* outPointerIcon) {
+ outPointerIcon->reset();
+
+ if (!pointerIconObj) {
+ return OK;
+ }
+
+ jobject loadedPointerIconObj = env->CallObjectMethod(pointerIconObj,
+ gPointerIconClassInfo.load, contextObj);
+ if (env->ExceptionCheck() || !loadedPointerIconObj) {
+ LOGW("An exception occurred while loading a pointer icon.");
+ LOGW_EX(env);
+ env->ExceptionClear();
+ return UNKNOWN_ERROR;
+ }
+
+ outPointerIcon->style = env->GetIntField(loadedPointerIconObj,
+ gPointerIconClassInfo.mStyle);
+ outPointerIcon->hotSpotX = env->GetFloatField(loadedPointerIconObj,
+ gPointerIconClassInfo.mHotSpotX);
+ outPointerIcon->hotSpotY = env->GetFloatField(loadedPointerIconObj,
+ gPointerIconClassInfo.mHotSpotY);
+
+ jobject bitmapObj = env->GetObjectField(loadedPointerIconObj, gPointerIconClassInfo.mBitmap);
+ if (bitmapObj) {
+ SkBitmap* bitmap = GraphicsJNI::getNativeBitmap(env, bitmapObj);
+ if (bitmap) {
+ outPointerIcon->bitmap = *bitmap; // use a shared pixel ref
+ }
+ env->DeleteLocalRef(bitmapObj);
+ }
+
+ env->DeleteLocalRef(loadedPointerIconObj);
+ return OK;
+}
+
+status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env, jobject contextObj,
+ int32_t style, PointerIcon* outPointerIcon) {
+ jobject pointerIconObj = android_view_PointerIcon_getSystemIcon(env, contextObj, style);
+ if (!pointerIconObj) {
+ outPointerIcon->reset();
+ return UNKNOWN_ERROR;
+ }
+
+ status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+ contextObj, outPointerIcon);
+ env->DeleteLocalRef(pointerIconObj);
+ return status;
+}
+
+
+// --- JNI Registration ---
+
+#define FIND_CLASS(var, className) \
+ var = env->FindClass(className); \
+ LOG_FATAL_IF(! var, "Unable to find class " className); \
+ var = jclass(env->NewGlobalRef(var));
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+ var = env->GetMethodID(clazz, methodName, methodDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
+ var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
+ LOG_FATAL_IF(! var, "Unable to find field " fieldName);
+
+int register_android_view_PointerIcon(JNIEnv* env) {
+ FIND_CLASS(gPointerIconClassInfo.clazz, "android/view/PointerIcon");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mBitmap, gPointerIconClassInfo.clazz,
+ "mBitmap", "Landroid/graphics/Bitmap;");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mStyle, gPointerIconClassInfo.clazz,
+ "mStyle", "I");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mHotSpotX, gPointerIconClassInfo.clazz,
+ "mHotSpotX", "F");
+
+ GET_FIELD_ID(gPointerIconClassInfo.mHotSpotY, gPointerIconClassInfo.clazz,
+ "mHotSpotY", "F");
+
+ GET_STATIC_METHOD_ID(gPointerIconClassInfo.getSystemIcon, gPointerIconClassInfo.clazz,
+ "getSystemIcon", "(Landroid/content/Context;I)Landroid/view/PointerIcon;");
+
+ GET_METHOD_ID(gPointerIconClassInfo.load, gPointerIconClassInfo.clazz,
+ "load", "(Landroid/content/Context;)Landroid/view/PointerIcon;");
+
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_view_PointerIcon.h b/core/jni/android_view_PointerIcon.h
new file mode 100644
index 0000000..3bfd645
--- /dev/null
+++ b/core/jni/android_view_PointerIcon.h
@@ -0,0 +1,80 @@
+/*
+ * 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_VIEW_POINTER_ICON_H
+#define _ANDROID_VIEW_POINTER_ICON_H
+
+#include "jni.h"
+
+#include <utils/Errors.h>
+#include <SkBitmap.h>
+
+namespace android {
+
+/* Pointer icon styles.
+ * Must match the definition in android.view.PointerIcon.
+ */
+enum {
+ POINTER_ICON_STYLE_CUSTOM = -1,
+ POINTER_ICON_STYLE_NULL = 0,
+ POINTER_ICON_STYLE_ARROW = 1000,
+ POINTER_ICON_STYLE_SPOT_HOVER = 2000,
+ POINTER_ICON_STYLE_SPOT_TOUCH = 2001,
+ POINTER_ICON_STYLE_SPOT_ANCHOR = 2002,
+};
+
+/*
+ * Describes a pointer icon.
+ */
+struct PointerIcon {
+ inline PointerIcon() {
+ reset();
+ }
+
+ int32_t style;
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+
+ inline bool isNullIcon() {
+ return style == POINTER_ICON_STYLE_NULL;
+ }
+
+ inline void reset() {
+ style = POINTER_ICON_STYLE_NULL;
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+ }
+};
+
+/* Gets a system pointer icon with the specified style. */
+extern jobject android_view_PointerIcon_getSystemIcon(JNIEnv* env,
+ jobject contextObj, int32_t style);
+
+/* Loads the bitmap associated with a pointer icon.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_load(JNIEnv* env,
+ jobject pointerIconObj, jobject contextObj, PointerIcon* outPointerIcon);
+
+/* Loads the bitmap associated with a pointer icon by style.
+ * If pointerIconObj is NULL, returns OK and a pointer icon with POINTER_ICON_STYLE_NULL. */
+extern status_t android_view_PointerIcon_loadSystemIcon(JNIEnv* env,
+ jobject contextObj, int32_t style, PointerIcon* outPointerIcon);
+
+} // namespace android
+
+#endif // _ANDROID_OS_POINTER_ICON_H
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor.png b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
new file mode 100644
index 0000000..d7aca36
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
new file mode 100644
index 0000000..2222b8e
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_anchor_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_anchor"
+ android:hotSpotX="33"
+ android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover.png b/core/res/res/drawable-mdpi/pointer_spot_hover.png
new file mode 100644
index 0000000..5041aa3
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
new file mode 100644
index 0000000..dc62a69
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_hover_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_hover"
+ android:hotSpotX="33"
+ android:hotSpotY="33" />
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch.png b/core/res/res/drawable-mdpi/pointer_spot_touch.png
new file mode 100644
index 0000000..64a42a1
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
new file mode 100644
index 0000000..4bffee6
--- /dev/null
+++ b/core/res/res/drawable-mdpi/pointer_spot_touch_icon.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ android:bitmap="@drawable/pointer_spot_touch"
+ android:hotSpotX="24"
+ android:hotSpotY="24" />
diff --git a/core/res/res/layout/action_menu_layout.xml b/core/res/res/layout/action_menu_layout.xml
index 18d5531..5696d87 100644
--- a/core/res/res/layout/action_menu_layout.xml
+++ b/core/res/res/layout/action_menu_layout.xml
@@ -17,4 +17,7 @@
<com.android.internal.view.menu.ActionMenuView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
- android:layout_height="wrap_content" />
+ android:layout_height="wrap_content"
+ android:divider="?android:attr/dividerVertical"
+ android:dividerPadding="12dip"
+ android:gravity="center_vertical" />
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 819ce58..e8767d8 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -757,6 +757,13 @@
<!-- Default style for the Switch widget. -->
<attr name="switchStyle" format="reference" />
+ <!-- ============== -->
+ <!-- Pointer styles -->
+ <!-- ============== -->
+ <eat-comment />
+
+ <!-- Reference to the Pointer style -->
+ <attr name="pointerStyle" format="reference" />
</declare-styleable>
<!-- **************************************************************** -->
@@ -4921,6 +4928,17 @@
<attr name="switchPadding" format="dimension" />
</declare-styleable>
+ <declare-styleable name="Pointer">
+ <!-- Reference to a pointer icon drawable with STYLE_ARROW -->
+ <attr name="pointerIconArrow" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_HOVER -->
+ <attr name="pointerIconSpotHover" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_TOUCH -->
+ <attr name="pointerIconSpotTouch" format="reference" />
+ <!-- Reference to a pointer icon drawable with STYLE_SPOT_ANCHOR -->
+ <attr name="pointerIconSpotAnchor" format="reference" />
+ </declare-styleable>
+
<declare-styleable name="PointerIcon">
<!-- Drawable to use as the icon bitmap. -->
<attr name="bitmap" format="reference" />
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index bf4c6d7..198ff8b 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -602,11 +602,10 @@
<item name="android:divider">@android:drawable/divider_horizontal_bright_opaque</item>
</style>
- <style name="Widget.ListView.Menu">
+ <style name="Widget.ListView.Menu" parent="Widget.Holo.ListView">
<item name="android:cacheColorHint">@null</item>
<item name="android:scrollbars">vertical</item>
<item name="android:fadingEdge">none</item>
- <item name="listSelector">@android:drawable/menu_selector</item>
<!-- Light background for the list in menus, so the divider for bright themes -->
<item name="android:divider">@android:drawable/divider_horizontal_dark</item>
</style>
@@ -2210,4 +2209,12 @@
<item name="android:borderLeft">0dip</item>
<item name="android:borderRight">0dip</item>
</style>
+
+ <!-- Pointer styles -->
+ <style name="Pointer">
+ <item name="android:pointerIconArrow">@android:drawable/pointer_arrow_icon</item>
+ <item name="android:pointerIconSpotHover">@android:drawable/pointer_spot_hover_icon</item>
+ <item name="android:pointerIconSpotTouch">@android:drawable/pointer_spot_touch_icon</item>
+ <item name="android:pointerIconSpotAnchor">@android:drawable/pointer_spot_anchor_icon</item>
+ </style>
</resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index b1e4f0f..b9fd6a5 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -323,6 +323,8 @@
<item name="fastScrollOverlayPosition">floating</item>
<item name="fastScrollTextColor">@android:color/primary_text_dark</item>
+ <!-- Pointer style -->
+ <item name="pointerStyle">@android:style/Pointer</item>
</style>
<!-- Variant of the default (dark) theme with no title bar -->
@@ -696,10 +698,10 @@
<!-- Menu Themes -->
<eat-comment />
- <style name="Theme.IconMenu">
+ <style name="Theme.IconMenu" parent="Theme.Holo">
<!-- Menu/item attributes -->
<item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item>
- <item name="android:itemBackground">@android:drawable/menu_selector</item>
+ <item name="android:itemBackground">?android:attr/selectableItemBackground</item>
<item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item>
<item name="android:horizontalDivider">@android:drawable/divider_horizontal_dark</item>
<item name="android:verticalDivider">@android:drawable/divider_vertical_dark</item>
@@ -708,7 +710,7 @@
<item name="android:background">@null</item>
</style>
- <style name="Theme.ExpandedMenu">
+ <style name="Theme.ExpandedMenu" parent="Theme.Holo">
<!-- Menu/item attributes -->
<item name="android:itemTextAppearance">?android:attr/textAppearanceLarge</item>
<item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item>
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index d9bf860..9347b27 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -59,6 +59,8 @@
private void assertLayout(Integer... expectedLayout) {
toggleMenu();
+ /* TODO These need to be rewritten to account for presenters that an activity
+ * does not have access to.
IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
int[] layout = iconMenuView.getLayout();
int layoutNumRows = iconMenuView.getLayoutNumRows();
@@ -70,6 +72,7 @@
assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
layout[row]);
}
+ */
}
public void test1ShortItem() {
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index ad746b07..b053699 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -58,6 +58,8 @@
private void assertLayout(Integer... expectedLayout) {
toggleMenu();
+ /* TODO These need to be rewritten to account for presenters that an activity
+ * does not have access to.
IconMenuView iconMenuView = ((IconMenuView) mActivity.getMenuView(MenuBuilder.TYPE_ICON));
int[] layout = iconMenuView.getLayout();
int layoutNumRows = iconMenuView.getLayoutNumRows();
@@ -69,6 +71,7 @@
assertEquals("Col mismatch on row " + row, expectedLayout[row].intValue(),
layout[row]);
}
+ */
}
public void test1ShortItem() {
diff --git a/core/tests/coretests/src/android/view/menu/MenuScenario.java b/core/tests/coretests/src/android/view/menu/MenuScenario.java
index b0b8802..668aec4 100644
--- a/core/tests/coretests/src/android/view/menu/MenuScenario.java
+++ b/core/tests/coretests/src/android/view/menu/MenuScenario.java
@@ -16,16 +16,12 @@
package android.view.menu;
-import android.util.ListScenario;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuBuilder.MenuAdapter;
-
import android.app.Activity;
import android.os.Bundle;
+import android.util.ListScenario;
import android.util.SparseArray;
import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
/**
* Utility base class for creating various Menu scenarios. Configurable by the
@@ -36,7 +32,6 @@
private Menu mMenu;
private MenuItem[] mItems;
private boolean[] mWasItemClicked;
- private MenuAdapter[] mMenuAdapters = new MenuAdapter[MenuBuilder.NUM_TYPES];
@Override
protected void onCreate(Bundle icicle) {
@@ -149,39 +144,6 @@
return -1;
}
- /**
- * @see MenuBuilder#getMenuAdapter(int)
- */
- public MenuAdapter getMenuAdapter(int menuType) {
- if (mMenuAdapters[menuType] == null) {
- mMenuAdapters[menuType] = ((MenuBuilder) mMenu).getMenuAdapter(menuType);
- }
-
- return mMenuAdapters[menuType];
- }
-
- /**
- * Gets a menu view. Call this after you're sure it has been shown,
- * otherwise it may not have the proper layout_* attributes set.
- *
- * @param menuType The type of menu.
- * @return The MenuView for that type.
- */
- public View getMenuView(int menuType) {
- return ((MenuBuilder) mMenu).getMenuView(menuType, null);
- }
-
- /**
- * Gets the menu item view for a given position.
- *
- * @param menuType The type of menu.
- * @param position The position of the item.
- * @return The menu item view for the given item in the given menu type.
- */
- public View getItemView(int menuType, int position) {
- return getMenuAdapter(menuType).getView(position, null, null);
- }
-
public static class Params {
// Using as data structure, so no m prefix
private boolean shouldShowMenu = true;
diff --git a/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
index 4e71053..82ad858 100644
--- a/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuWith1ItemTest.java
@@ -61,6 +61,9 @@
@LargeTest
public void testTouchModeTransfersRemovesFocus() throws Exception {
+ /* TODO These need to be rewritten to account for presenters that an activity
+ * does not have access to.
+
// open menu, move around to give it focus
sendKeys(KeyEvent.KEYCODE_MENU, KeyEvent.KEYCODE_DPAD_LEFT);
final View menuItem = mActivity.getItemView(MenuBuilder.TYPE_ICON, 0);
@@ -80,5 +83,6 @@
sendKeys(KeyEvent.KEYCODE_MENU);
assertTrue("menuItem.isInTouchMode()", menuItem.isInTouchMode());
assertFalse("menuItem.isFocused()", menuItem.isFocused());
+ */
}
}
diff --git a/core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
similarity index 87%
rename from core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java
rename to core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
index b6f8be5..ab6b2b6 100644
--- a/core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
@@ -22,9 +22,9 @@
import android.os.Message;
import android.os.SystemClock;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
-import com.android.internal.util.HierarchicalStateMachine.ProcessedMessageInfo;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.internal.util.StateMachine.ProcessedMessageInfo;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -33,9 +33,9 @@
import junit.framework.TestCase;
/**
- * Test for HierarchicalStateMachine.
+ * Test for StateMachine.
*/
-public class HierarchicalStateMachineTest extends TestCase {
+public class StateMachineTest extends TestCase {
private static final int TEST_CMD_1 = 1;
private static final int TEST_CMD_2 = 2;
private static final int TEST_CMD_3 = 3;
@@ -45,12 +45,12 @@
private static final boolean DBG = true;
private static final boolean WAIT_FOR_DEBUGGER = false;
- private static final String TAG = "HierarchicalStateMachineTest";
+ private static final String TAG = "StateMachineTest";
/**
* Tests that we can quit the state machine.
*/
- class StateMachineQuitTest extends HierarchicalStateMachine {
+ class StateMachineQuitTest extends StateMachine {
private int mQuitCount = 0;
StateMachineQuitTest(String name) {
@@ -65,8 +65,9 @@
setInitialState(mS1);
}
- class S1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (isQuit(message)) {
mQuitCount += 1;
if (mQuitCount > 2) {
@@ -129,18 +130,18 @@
// The first two message didn't quit and were handled by mS1
pmi = smQuitTest.getProcessedMessageInfo(6);
- assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
assertEquals(smQuitTest.mS1, pmi.getState());
assertEquals(smQuitTest.mS1, pmi.getOriginalState());
pmi = smQuitTest.getProcessedMessageInfo(7);
- assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
assertEquals(smQuitTest.mS1, pmi.getState());
assertEquals(smQuitTest.mS1, pmi.getOriginalState());
// The last message was never handled so the states are null
pmi = smQuitTest.getProcessedMessageInfo(8);
- assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
+ assertEquals(StateMachine.SM_QUIT_CMD, pmi.getWhat());
assertEquals(null, pmi.getState());
assertEquals(null, pmi.getOriginalState());
@@ -150,7 +151,7 @@
/**
* Test enter/exit can use transitionTo
*/
- class StateMachineEnterExitTransitionToTest extends HierarchicalStateMachine {
+ class StateMachineEnterExitTransitionToTest extends StateMachine {
StateMachineEnterExitTransitionToTest(String name) {
super(name);
mThisSm = this;
@@ -166,34 +167,38 @@
setInitialState(mS1);
}
- class S1 extends HierarchicalState {
- @Override protected void enter() {
+ class S1 extends State {
+ @Override
+ public void enter() {
// Test that message is HSM_INIT_CMD
- assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
// Test that a transition in enter and the initial state works
mS1EnterCount += 1;
transitionTo(mS2);
Log.d(TAG, "S1.enter");
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
// Test that message is HSM_INIT_CMD
- assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
mS1ExitCount += 1;
Log.d(TAG, "S1.exit");
}
}
- class S2 extends HierarchicalState {
- @Override protected void enter() {
+ class S2 extends State {
+ @Override
+ public void enter() {
// Test that message is HSM_INIT_CMD
- assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
+ assertEquals(SM_INIT_CMD, getCurrentMessage().what);
mS2EnterCount += 1;
Log.d(TAG, "S2.enter");
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
// Test that message is TEST_CMD_1
assertEquals(TEST_CMD_1, getCurrentMessage().what);
@@ -202,7 +207,8 @@
transitionTo(mS4);
Log.d(TAG, "S2.exit");
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public boolean processMessage(Message message) {
// Start a transition to S3 but it will be
// changed to a transition to S4 in exit
transitionTo(mS3);
@@ -211,28 +217,32 @@
}
}
- class S3 extends HierarchicalState {
- @Override protected void enter() {
+ class S3 extends State {
+ @Override
+ public void enter() {
// Test that we can do halting in an enter/exit
transitionToHaltingState();
mS3EnterCount += 1;
Log.d(TAG, "S3.enter");
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
mS3ExitCount += 1;
Log.d(TAG, "S3.exit");
}
}
- class S4 extends HierarchicalState {
- @Override protected void enter() {
+ class S4 extends State {
+ @Override
+ public void enter() {
// Test that we can do halting in an enter/exit
transitionToHaltingState();
mS4EnterCount += 1;
Log.d(TAG, "S4.enter");
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
mS4ExitCount += 1;
Log.d(TAG, "S4.exit");
}
@@ -310,7 +320,7 @@
/**
* Tests that ProcessedMessage works as a circular buffer.
*/
- class StateMachine0 extends HierarchicalStateMachine {
+ class StateMachine0 extends StateMachine {
StateMachine0(String name) {
super(name);
mThisSm = this;
@@ -324,8 +334,9 @@
setInitialState(mS1);
}
- class S1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_6) {
transitionToHaltingState();
}
@@ -394,7 +405,7 @@
* in state mS1. With the first message it transitions to
* itself which causes it to be exited and reentered.
*/
- class StateMachine1 extends HierarchicalStateMachine {
+ class StateMachine1 extends StateMachine {
StateMachine1(String name) {
super(name);
mThisSm = this;
@@ -408,12 +419,17 @@
if (DBG) Log.d(TAG, "StateMachine1: ctor X");
}
- class S1 extends HierarchicalState {
- @Override protected void enter() {
+ class S1 extends State {
+ @Override
+ public void enter() {
mEnterCount++;
}
-
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mExitCount++;
+ }
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_1) {
assertEquals(1, mEnterCount);
assertEquals(0, mExitCount);
@@ -425,10 +441,6 @@
}
return HANDLED;
}
-
- @Override protected void exit() {
- mExitCount++;
- }
}
@Override
@@ -493,7 +505,7 @@
* mS2 then receives both of the deferred messages first TEST_CMD_1 and
* then TEST_CMD_2.
*/
- class StateMachine2 extends HierarchicalStateMachine {
+ class StateMachine2 extends StateMachine {
StateMachine2(String name) {
super(name);
mThisSm = this;
@@ -508,26 +520,28 @@
if (DBG) Log.d(TAG, "StateMachine2: ctor X");
}
- class S1 extends HierarchicalState {
- @Override protected void enter() {
+ class S1 extends State {
+ @Override
+ public void enter() {
mDidEnter = true;
}
-
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mDidExit = true;
+ }
+ @Override
+ public boolean processMessage(Message message) {
deferMessage(message);
if (message.what == TEST_CMD_2) {
transitionTo(mS2);
}
return HANDLED;
}
-
- @Override protected void exit() {
- mDidExit = true;
- }
}
- class S2 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
@@ -598,7 +612,7 @@
* Test that unhandled messages in a child are handled by the parent.
* When TEST_CMD_2 is received.
*/
- class StateMachine3 extends HierarchicalStateMachine {
+ class StateMachine3 extends StateMachine {
StateMachine3(String name) {
super(name);
mThisSm = this;
@@ -615,8 +629,9 @@
if (DBG) Log.d(TAG, "StateMachine3: ctor X");
}
- class ParentState extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class ParentState extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
@@ -624,8 +639,9 @@
}
}
- class ChildState extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class ChildState extends State {
+ @Override
+ public boolean processMessage(Message message) {
return NOT_HANDLED;
}
}
@@ -682,7 +698,7 @@
* with transition from child 1 to child 2 and child 2
* lets the parent handle the messages.
*/
- class StateMachine4 extends HierarchicalStateMachine {
+ class StateMachine4 extends StateMachine {
StateMachine4(String name) {
super(name);
mThisSm = this;
@@ -700,8 +716,9 @@
if (DBG) Log.d(TAG, "StateMachine4: ctor X");
}
- class ParentState extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class ParentState extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
@@ -709,15 +726,17 @@
}
}
- class ChildState1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class ChildState1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
transitionTo(mChildState2);
return HANDLED;
}
}
- class ChildState2 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class ChildState2 extends State {
+ @Override
+ public boolean processMessage(Message message) {
return NOT_HANDLED;
}
}
@@ -775,7 +794,7 @@
* Test transition from one child to another of a "complex"
* hierarchy with two parents and multiple children.
*/
- class StateMachine5 extends HierarchicalStateMachine {
+ class StateMachine5 extends StateMachine {
StateMachine5(String name) {
super(name);
mThisSm = this;
@@ -797,23 +816,32 @@
if (DBG) Log.d(TAG, "StateMachine5: ctor X");
}
- class ParentState1 extends HierarchicalState {
- @Override protected void enter() {
+ class ParentState1 extends State {
+ @Override
+ public void enter() {
mParentState1EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
- return HANDLED;
- }
- @Override protected void exit() {
+ @Override
+ public void exit() {
mParentState1ExitCount += 1;
}
+ @Override
+ public boolean processMessage(Message message) {
+ return HANDLED;
+ }
}
- class ChildState1 extends HierarchicalState {
- @Override protected void enter() {
+ class ChildState1 extends State {
+ @Override
+ public void enter() {
mChildState1EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mChildState1ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(0, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -832,16 +860,19 @@
transitionTo(mChildState2);
return HANDLED;
}
- @Override protected void exit() {
- mChildState1ExitCount += 1;
- }
}
- class ChildState2 extends HierarchicalState {
- @Override protected void enter() {
+ class ChildState2 extends State {
+ @Override
+ public void enter() {
mChildState2EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mChildState2ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(0, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -860,16 +891,19 @@
transitionTo(mChildState5);
return HANDLED;
}
- @Override protected void exit() {
- mChildState2ExitCount += 1;
- }
}
- class ParentState2 extends HierarchicalState {
- @Override protected void enter() {
+ class ParentState2 extends State {
+ @Override
+ public void enter() {
mParentState2EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mParentState2ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(1, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -888,16 +922,19 @@
transitionToHaltingState();
return HANDLED;
}
- @Override protected void exit() {
- mParentState2ExitCount += 1;
- }
}
- class ChildState3 extends HierarchicalState {
- @Override protected void enter() {
+ class ChildState3 extends State {
+ @Override
+ public void enter() {
mChildState3EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mChildState3ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(1, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -916,16 +953,19 @@
transitionTo(mChildState4);
return HANDLED;
}
- @Override protected void exit() {
- mChildState3ExitCount += 1;
- }
}
- class ChildState4 extends HierarchicalState {
- @Override protected void enter() {
+ class ChildState4 extends State {
+ @Override
+ public void enter() {
mChildState4EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mChildState4ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(1, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -944,16 +984,19 @@
transitionTo(mParentState2);
return HANDLED;
}
- @Override protected void exit() {
- mChildState4ExitCount += 1;
- }
}
- class ChildState5 extends HierarchicalState {
- @Override protected void enter() {
+ class ChildState5 extends State {
+ @Override
+ public void enter() {
mChildState5EnterCount += 1;
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ mChildState5ExitCount += 1;
+ }
+ @Override
+ public boolean processMessage(Message message) {
assertEquals(1, mParentState1EnterCount);
assertEquals(1, mParentState1ExitCount);
assertEquals(1, mChildState1EnterCount);
@@ -972,9 +1015,6 @@
transitionTo(mChildState3);
return HANDLED;
}
- @Override protected void exit() {
- mChildState5ExitCount += 1;
- }
}
@Override
@@ -1089,7 +1129,7 @@
* after construction and before any other messages arrive and that
* sendMessageDelayed works.
*/
- class StateMachine6 extends HierarchicalStateMachine {
+ class StateMachine6 extends StateMachine {
StateMachine6(String name) {
super(name);
mThisSm = this;
@@ -1103,13 +1143,13 @@
if (DBG) Log.d(TAG, "StateMachine6: ctor X");
}
- class S1 extends HierarchicalState {
-
- @Override protected void enter() {
+ class S1 extends State {
+ @Override
+ public void enter() {
sendMessage(TEST_CMD_1);
}
-
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_1) {
mArrivalTimeMsg1 = SystemClock.elapsedRealtime();
} else if (message.what == TEST_CMD_2) {
@@ -1118,9 +1158,6 @@
}
return HANDLED;
}
-
- @Override protected void exit() {
- }
}
@Override
@@ -1178,7 +1215,7 @@
* Test that enter is invoked immediately after exit. This validates
* that enter can be used to send a watch dog message for its state.
*/
- class StateMachine7 extends HierarchicalStateMachine {
+ class StateMachine7 extends StateMachine {
private final int SM7_DELAY_TIME = 250;
StateMachine7(String name) {
@@ -1195,24 +1232,26 @@
if (DBG) Log.d(TAG, "StateMachine7: ctor X");
}
- class S1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S1 extends State {
+ @Override
+ public void exit() {
+ sendMessage(TEST_CMD_2);
+ }
+ @Override
+ public boolean processMessage(Message message) {
transitionTo(mS2);
return HANDLED;
}
- @Override protected void exit() {
- sendMessage(TEST_CMD_2);
- }
}
- class S2 extends HierarchicalState {
-
- @Override protected void enter() {
+ class S2 extends State {
+ @Override
+ public void enter() {
// Send a delayed message as a watch dog
sendMessageDelayed(TEST_CMD_3, SM7_DELAY_TIME);
}
-
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_2) {
mMsgCount += 1;
mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
@@ -1226,9 +1265,6 @@
}
return HANDLED;
}
-
- @Override protected void exit() {
- }
}
@Override
@@ -1286,7 +1322,7 @@
/**
* Test unhandledMessage.
*/
- class StateMachineUnhandledMessage extends HierarchicalStateMachine {
+ class StateMachineUnhandledMessage extends StateMachine {
StateMachineUnhandledMessage(String name) {
super(name);
mThisSm = this;
@@ -1298,13 +1334,14 @@
// Set the initial state
setInitialState(mS1);
}
-
- @Override protected void unhandledMessage(Message message) {
+ @Override
+ public void unhandledMessage(Message message) {
mUnhandledMessageCount += 1;
}
- class S1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
@@ -1359,7 +1396,7 @@
* will be used to notify testStateMachineSharedThread that the test is
* complete.
*/
- class StateMachineSharedThread extends HierarchicalStateMachine {
+ class StateMachineSharedThread extends StateMachine {
StateMachineSharedThread(String name, Looper looper, int maxCount) {
super(name, looper);
mMaxCount = maxCount;
@@ -1372,8 +1409,9 @@
setInitialState(mS1);
}
- class S1 extends HierarchicalState {
- @Override protected boolean processMessage(Message message) {
+ class S1 extends State {
+ @Override
+ public boolean processMessage(Message message) {
if (message.what == TEST_CMD_4) {
transitionToHaltingState();
}
@@ -1503,7 +1541,7 @@
}
}
-class Hsm1 extends HierarchicalStateMachine {
+class Hsm1 extends StateMachine {
private static final String TAG = "hsm1";
public static final int CMD_1 = 1;
@@ -1535,11 +1573,17 @@
Log.d(TAG, "ctor X");
}
- class P1 extends HierarchicalState {
- @Override protected void enter() {
+ class P1 extends State {
+ @Override
+ public void enter() {
Log.d(TAG, "P1.enter");
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ Log.d(TAG, "P1.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
boolean retVal;
Log.d(TAG, "P1.processMessage what=" + message.what);
switch(message.what) {
@@ -1557,16 +1601,19 @@
}
return retVal;
}
- @Override protected void exit() {
- Log.d(TAG, "P1.exit");
- }
}
- class S1 extends HierarchicalState {
- @Override protected void enter() {
+ class S1 extends State {
+ @Override
+ public void enter() {
Log.d(TAG, "S1.enter");
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ Log.d(TAG, "S1.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
Log.d(TAG, "S1.processMessage what=" + message.what);
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
@@ -1577,16 +1624,19 @@
return NOT_HANDLED;
}
}
- @Override protected void exit() {
- Log.d(TAG, "S1.exit");
- }
}
- class S2 extends HierarchicalState {
- @Override protected void enter() {
+ class S2 extends State {
+ @Override
+ public void enter() {
Log.d(TAG, "S2.enter");
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ Log.d(TAG, "S2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
boolean retVal;
Log.d(TAG, "S2.processMessage what=" + message.what);
switch(message.what) {
@@ -1605,17 +1655,20 @@
}
return retVal;
}
- @Override protected void exit() {
- Log.d(TAG, "S2.exit");
- }
}
- class P2 extends HierarchicalState {
- @Override protected void enter() {
+ class P2 extends State {
+ @Override
+ public void enter() {
Log.d(TAG, "P2.enter");
sendMessage(CMD_5);
}
- @Override protected boolean processMessage(Message message) {
+ @Override
+ public void exit() {
+ Log.d(TAG, "P2.exit");
+ }
+ @Override
+ public boolean processMessage(Message message) {
Log.d(TAG, "P2.processMessage what=" + message.what);
switch(message.what) {
case(CMD_3):
@@ -1628,9 +1681,6 @@
}
return HANDLED;
}
- @Override protected void exit() {
- Log.d(TAG, "P2.exit");
- }
}
@Override
diff --git a/docs/html/sdk/oem-usb.jd b/docs/html/sdk/oem-usb.jd
index 882a433..7d7edfe 100644
--- a/docs/html/sdk/oem-usb.jd
+++ b/docs/html/sdk/oem-usb.jd
@@ -90,5 +90,5 @@
<td>Sony Ericsson</td> <td><a
href="http://developer.sonyericsson.com/wportal/devworld/search-downloads/android">http://developer.sonyericsson.com/wportal/devworld/search-downloads/android</a></td></tr><tr>
<td>ZTE</td> <td><a
-href="http://www.zte.com.cn/cn/products/mobile/services_support/index.jsp">http://www.zte.com.cn/cn/products/mobile/services_support/index.jsp</a></td></tr>
+href="http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442">http://support.zte.com.cn/support/news/NewsDetail.aspx?newsId=1000442</a></td></tr>
</table>
diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h
index 340daaf..96828c6 100644
--- a/include/gui/SurfaceTexture.h
+++ b/include/gui/SurfaceTexture.h
@@ -127,11 +127,28 @@
// be called from the client.
status_t setDefaultBufferSize(uint32_t w, uint32_t h);
-private:
+ // getCurrentBuffer returns the buffer associated with the current image.
+ sp<GraphicBuffer> getCurrentBuffer() const;
+
+ // getCurrentTextureTarget returns the texture target of the current
+ // texture as returned by updateTexImage().
+ GLenum getCurrentTextureTarget() const;
+
+ // getCurrentCrop returns the cropping rectangle of the current buffer
+ Rect getCurrentCrop() const;
+
+ // getCurrentTransform returns the transform of the current buffer
+ uint32_t getCurrentTransform() const;
+
+protected:
// freeAllBuffers frees the resources (both GraphicBuffer and EGLImage) for
// all slots.
void freeAllBuffers();
+ static bool isExternalFormat(uint32_t format);
+ static GLenum getTextureTarget(uint32_t format);
+
+private:
// createImage creates a new EGLImage from a GraphicBuffer.
EGLImageKHR createImage(EGLDisplay dpy,
@@ -194,6 +211,10 @@
// reset mCurrentTexture to INVALID_BUFFER_SLOT.
int mCurrentTexture;
+ // mCurrentTextureTarget is the GLES texture target to be used with the
+ // current texture.
+ GLenum mCurrentTextureTarget;
+
// mCurrentTextureBuf is the graphic buffer of the current texture. It's
// possible that this buffer is not associated with any buffer slot, so we
// must track it separately in order to properly use
@@ -256,7 +277,7 @@
// mMutex is the mutex used to prevent concurrent access to the member
// variables of SurfaceTexture objects. It must be locked whenever the
// member variables are accessed.
- Mutex mMutex;
+ mutable Mutex mMutex;
};
// ----------------------------------------------------------------------------
diff --git a/include/gui/SurfaceTextureClient.h b/include/gui/SurfaceTextureClient.h
index df82bf2..fe9b049 100644
--- a/include/gui/SurfaceTextureClient.h
+++ b/include/gui/SurfaceTextureClient.h
@@ -27,6 +27,8 @@
namespace android {
+class Surface;
+
class SurfaceTextureClient
: public EGLNativeBase<ANativeWindow, SurfaceTextureClient, RefBase>
{
@@ -36,6 +38,7 @@
sp<ISurfaceTexture> getISurfaceTexture() const;
private:
+ friend class Surface;
// can't be copied
SurfaceTextureClient& operator = (const SurfaceTextureClient& rhs);
@@ -78,6 +81,8 @@
void freeAllBuffers();
+ int getConnectedApi() const;
+
enum { MIN_UNDEQUEUED_BUFFERS = SurfaceTexture::MIN_UNDEQUEUED_BUFFERS };
enum { MIN_BUFFER_SLOTS = SurfaceTexture::MIN_BUFFER_SLOTS };
enum { NUM_BUFFER_SLOTS = SurfaceTexture::NUM_BUFFER_SLOTS };
@@ -121,10 +126,25 @@
// a timestamp is auto-generated when queueBuffer is called.
int64_t mTimestamp;
+ // mConnectedApi holds the currently connected API to this surface
+ int mConnectedApi;
+
+ // mQueryWidth is the width returned by query(). It is set to width
+ // of the last dequeued buffer or to mReqWidth if no buffer was dequeued.
+ uint32_t mQueryWidth;
+
+ // mQueryHeight is the height returned by query(). It is set to height
+ // of the last dequeued buffer or to mReqHeight if no buffer was dequeued.
+ uint32_t mQueryHeight;
+
+ // mQueryFormat is the format returned by query(). It is set to the last
+ // dequeued format or to mReqFormat if no buffer was dequeued.
+ uint32_t mQueryFormat;
+
// mMutex is the mutex used to prevent concurrent access to the member
// variables of SurfaceTexture objects. It must be locked whenever the
// member variables are accessed.
- Mutex mMutex;
+ mutable Mutex mMutex;
};
}; // namespace android
diff --git a/include/ui/Input.h b/include/ui/Input.h
index 0dc29c8..9b92c73 100644
--- a/include/ui/Input.h
+++ b/include/ui/Input.h
@@ -620,6 +620,11 @@
// Oldest sample to consider when calculating the velocity.
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
+ // When the total duration of the window of samples being averaged is less
+ // than the window size, the resulting velocity is scaled to reduce the impact
+ // of overestimation in short traces.
+ static const nsecs_t MIN_WINDOW = 100 * 1000000; // 100 ms
+
// The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 95cfddf..392193b 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -851,6 +851,9 @@
tr.target.handle = handle;
tr.code = code;
tr.flags = binderFlags;
+ tr.cookie = 0;
+ tr.sender_pid = 0;
+ tr.sender_euid = 0;
const status_t err = data.errorCheck();
if (err == NO_ERROR) {
diff --git a/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp
index e2346f0..39418f0 100644
--- a/libs/gui/SurfaceTexture.cpp
+++ b/libs/gui/SurfaceTexture.cpp
@@ -27,6 +27,8 @@
#include <gui/SurfaceTexture.h>
+#include <hardware/hardware.h>
+
#include <surfaceflinger/ISurfaceComposer.h>
#include <surfaceflinger/SurfaceComposerClient.h>
#include <surfaceflinger/IGraphicBufferAlloc.h>
@@ -82,6 +84,7 @@
mUseDefaultSize(true),
mBufferCount(MIN_BUFFER_SLOTS),
mCurrentTexture(INVALID_BUFFER_SLOT),
+ mCurrentTextureTarget(GL_TEXTURE_EXTERNAL_OES),
mCurrentTransform(0),
mCurrentTimestamp(0),
mLastQueued(INVALID_BUFFER_SLOT),
@@ -197,6 +200,7 @@
if (buffer == NULL) {
return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION;
}
+
if ((mUseDefaultSize) &&
((uint32_t(buffer->width) != mDefaultWidth) ||
(uint32_t(buffer->height) != mDefaultHeight))) {
@@ -263,9 +267,6 @@
LOGV("SurfaceTexture::updateTexImage");
Mutex::Autolock lock(mMutex);
- // We always bind the texture even if we don't update its contents.
- glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
-
// Initially both mCurrentTexture and mLastQueued are INVALID_BUFFER_SLOT,
// so this check will fail until a buffer gets queued.
if (mCurrentTexture != mLastQueued) {
@@ -283,7 +284,15 @@
while ((error = glGetError()) != GL_NO_ERROR) {
LOGE("GL error cleared before updating SurfaceTexture: %#04x", error);
}
- glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, (GLeglImageOES)image);
+
+ GLenum target = getTextureTarget(
+ mSlots[mLastQueued].mGraphicBuffer->format);
+ if (target != mCurrentTextureTarget) {
+ glDeleteTextures(1, &mTexName);
+ }
+ glBindTexture(target, mTexName);
+ glEGLImageTargetTexture2DOES(target, (GLeglImageOES)image);
+
bool failed = false;
while ((error = glGetError()) != GL_NO_ERROR) {
LOGE("error binding external texture image %p (slot %d): %#04x",
@@ -296,14 +305,53 @@
// Update the SurfaceTexture state.
mCurrentTexture = mLastQueued;
+ mCurrentTextureTarget = target;
mCurrentTextureBuf = mSlots[mCurrentTexture].mGraphicBuffer;
mCurrentCrop = mLastQueuedCrop;
mCurrentTransform = mLastQueuedTransform;
mCurrentTimestamp = mLastQueuedTimestamp;
+ } else {
+ // We always bind the texture even if we don't update its contents.
+ glBindTexture(mCurrentTextureTarget, mTexName);
}
return OK;
}
+bool SurfaceTexture::isExternalFormat(uint32_t format)
+{
+ switch (format) {
+ // supported YUV formats
+ case HAL_PIXEL_FORMAT_YV12:
+ // Legacy/deprecated YUV formats
+ case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+ case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+ case HAL_PIXEL_FORMAT_YCbCr_422_I:
+ return true;
+ }
+
+ // Any OEM format needs to be considered
+ if (format>=0x100 && format<=0x1FF)
+ return true;
+
+ return false;
+}
+
+GLenum SurfaceTexture::getTextureTarget(uint32_t format)
+{
+ GLenum target = GL_TEXTURE_2D;
+#if defined(GL_OES_EGL_image_external)
+ if (isExternalFormat(format)) {
+ target = GL_TEXTURE_EXTERNAL_OES;
+ }
+#endif
+ return target;
+}
+
+GLenum SurfaceTexture::getCurrentTextureTarget() const {
+ Mutex::Autolock lock(mMutex);
+ return mCurrentTextureTarget;
+}
+
void SurfaceTexture::getTransformMatrix(float mtx[16]) {
LOGV("SurfaceTexture::getTransformMatrix");
Mutex::Autolock lock(mMutex);
@@ -445,6 +493,22 @@
return image;
}
+sp<GraphicBuffer> SurfaceTexture::getCurrentBuffer() const {
+ Mutex::Autolock lock(mMutex);
+ return mCurrentTextureBuf;
+}
+
+Rect SurfaceTexture::getCurrentCrop() const {
+ Mutex::Autolock lock(mMutex);
+ return mCurrentCrop;
+}
+
+uint32_t SurfaceTexture::getCurrentTransform() const {
+ Mutex::Autolock lock(mMutex);
+ return mCurrentTransform;
+}
+
+
static void mtxMul(float out[16], const float a[16], const float b[16]) {
out[0] = a[0]*b[0] + a[4]*b[1] + a[8]*b[2] + a[12]*b[3];
out[1] = a[1]*b[0] + a[5]*b[1] + a[9]*b[2] + a[13]*b[3];
diff --git a/libs/gui/SurfaceTextureClient.cpp b/libs/gui/SurfaceTextureClient.cpp
index 29fc4d3..f4b2416 100644
--- a/libs/gui/SurfaceTextureClient.cpp
+++ b/libs/gui/SurfaceTextureClient.cpp
@@ -26,8 +26,10 @@
SurfaceTextureClient::SurfaceTextureClient(
const sp<ISurfaceTexture>& surfaceTexture):
mSurfaceTexture(surfaceTexture), mAllocator(0), mReqWidth(0),
- mReqHeight(0), mReqFormat(DEFAULT_FORMAT), mReqUsage(0),
- mTimestamp(NATIVE_WINDOW_TIMESTAMP_AUTO), mMutex() {
+ mReqHeight(0), mReqFormat(0), mReqUsage(0),
+ mTimestamp(NATIVE_WINDOW_TIMESTAMP_AUTO), mConnectedApi(0),
+ mQueryWidth(0), mQueryHeight(0), mQueryFormat(0),
+ mMutex() {
// Initialize the ANativeWindow function pointers.
ANativeWindow::setSwapInterval = setSwapInterval;
ANativeWindow::dequeueBuffer = dequeueBuffer;
@@ -101,9 +103,10 @@
}
sp<GraphicBuffer>& gbuf(mSlots[buf]);
if (err == ISurfaceTexture::BUFFER_NEEDS_REALLOCATION ||
- gbuf == 0 || gbuf->getWidth() != mReqWidth ||
- gbuf->getHeight() != mReqHeight ||
- uint32_t(gbuf->getPixelFormat()) != mReqFormat ||
+ gbuf == 0 ||
+ (mReqWidth && gbuf->getWidth() != mReqWidth) ||
+ (mReqHeight && gbuf->getHeight() != mReqHeight) ||
+ (mReqFormat && uint32_t(gbuf->getPixelFormat()) != mReqFormat) ||
(gbuf->getUsage() & mReqUsage) != mReqUsage) {
gbuf = mSurfaceTexture->requestBuffer(buf, mReqWidth, mReqHeight,
mReqFormat, mReqUsage);
@@ -111,6 +114,9 @@
LOGE("dequeueBuffer: ISurfaceTexture::requestBuffer failed");
return NO_MEMORY;
}
+ mQueryWidth = gbuf->width;
+ mQueryHeight = gbuf->height;
+ mQueryFormat = gbuf->format;
}
*buffer = gbuf.get();
return OK;
@@ -159,13 +165,13 @@
Mutex::Autolock lock(mMutex);
switch (what) {
case NATIVE_WINDOW_WIDTH:
+ *value = mQueryWidth ? mQueryWidth : mReqWidth;
+ return NO_ERROR;
case NATIVE_WINDOW_HEIGHT:
- // XXX: How should SurfaceTexture behave if setBuffersGeometry didn't
- // override the size?
- *value = 0;
+ *value = mQueryHeight ? mQueryHeight : mReqHeight;
return NO_ERROR;
case NATIVE_WINDOW_FORMAT:
- *value = DEFAULT_FORMAT;
+ *value = mQueryFormat ? mQueryFormat : mReqFormat;
return NO_ERROR;
case NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS:
*value = MIN_UNDEQUEUED_BUFFERS;
@@ -260,16 +266,49 @@
int SurfaceTextureClient::connect(int api) {
LOGV("SurfaceTextureClient::connect");
- // XXX: Implement this!
- return INVALID_OPERATION;
+ Mutex::Autolock lock(mMutex);
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ if (mConnectedApi) {
+ err = -EINVAL;
+ } else {
+ mConnectedApi = api;
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ return err;
}
int SurfaceTextureClient::disconnect(int api) {
LOGV("SurfaceTextureClient::disconnect");
- // XXX: Implement this!
- return INVALID_OPERATION;
+ Mutex::Autolock lock(mMutex);
+ int err = NO_ERROR;
+ switch (api) {
+ case NATIVE_WINDOW_API_EGL:
+ if (mConnectedApi == api) {
+ mConnectedApi = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ return err;
}
+int SurfaceTextureClient::getConnectedApi() const
+{
+ Mutex::Autolock lock(mMutex);
+ return mConnectedApi;
+}
+
+
int SurfaceTextureClient::setUsage(uint32_t reqUsage)
{
LOGV("SurfaceTextureClient::setUsage");
diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h
index 7d02cf8..faecadd 100644
--- a/libs/hwui/Caches.h
+++ b/libs/hwui/Caches.h
@@ -60,7 +60,10 @@
FV(1.0f, 1.0f, 1.0f, 1.0f)
};
static const GLsizei gMeshStride = sizeof(TextureVertex);
+static const GLsizei gVertexStride = sizeof(Vertex);
+static const GLsizei gAlphaVertexStride = sizeof(AlphaVertex);
static const GLsizei gMeshTextureOffset = 2 * sizeof(float);
+static const GLsizei gVertexAlphaOffset = 2 * sizeof(float);
static const GLsizei gMeshCount = 4;
///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 6fabbef..7f28959 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -882,6 +882,10 @@
mDescription.hasAlpha8Texture = isAlpha8;
}
+void OpenGLRenderer::setupDrawAALine() {
+ mDescription.hasWidth = true;
+}
+
void OpenGLRenderer::setupDrawPoint(float pointSize) {
mDescription.isPoint = true;
mDescription.pointSize = pointSize;
@@ -893,6 +897,7 @@
void OpenGLRenderer::setupDrawColor(int color, int alpha) {
mColorA = alpha / 255.0f;
+ // BUG on this next line? a is alpha divided by 255 *twice*
const float a = mColorA / 255.0f;
mColorR = a * ((color >> 16) & 0xFF);
mColorG = a * ((color >> 8) & 0xFF);
@@ -1060,6 +1065,37 @@
}
}
+void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) {
+ mCaches.unbindMeshBuffer();
+ glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
+ gVertexStride, vertices);
+}
+
+/**
+ * Sets up the shader to draw an AA line. We draw AA lines with quads, where there is an
+ * outer boundary that fades out to 0. The variables set in the shader define the width of the
+ * core line primitive ("width") and the width of the fading boundary ("boundaryWidth"). The
+ * "vtxDistance" attribute (one per vertex) is a value from zero to one that tells the fragment
+ * shader where the fragment is in relation to the line width overall; this value is then used
+ * to compute the proper color, based on whether the fragment lies in the fading AA region of
+ * the line.
+ */
+void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, float strokeWidth) {
+ mCaches.unbindMeshBuffer();
+ glVertexAttribPointer(mCaches.currentProgram->position, 2, GL_FLOAT, GL_FALSE,
+ gAlphaVertexStride, vertices);
+ int distanceSlot = mCaches.currentProgram->getAttrib("vtxDistance");
+ glEnableVertexAttribArray(distanceSlot);
+ glVertexAttribPointer(distanceSlot, 1, GL_FLOAT, GL_FALSE, gAlphaVertexStride, distanceCoords);
+ int widthSlot = mCaches.currentProgram->getUniform("width");
+ int boundaryWidthSlot = mCaches.currentProgram->getUniform("boundaryWidth");
+ int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth");
+ float boundaryWidth = (1 - strokeWidth) / 2;
+ glUniform1f(widthSlot, strokeWidth);
+ glUniform1f(boundaryWidthSlot, boundaryWidth);
+ glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidth));
+}
+
void OpenGLRenderer::finishDrawTexture() {
glDisableVertexAttribArray(mTexCoordsSlot);
}
@@ -1350,114 +1386,199 @@
}
}
+void OpenGLRenderer::drawLinesAsQuads(float *points, int count, bool isAA, bool isHairline,
+ float strokeWidth) {
+ int verticesCount = count;
+ if (count > 4) {
+ // Polyline: account for extra vertices needed for continous tri-strip
+ verticesCount += (count -4);
+ }
+ if (isAA) {
+ // Expand boundary to enable AA calculations on the quad border
+ strokeWidth += .5f;
+ }
+ Vertex lines[verticesCount];
+ Vertex* vertices = &lines[0];
+ AlphaVertex wLines[verticesCount];
+ AlphaVertex* aaVertices = &wLines[0];
+ if (!isAA) {
+ setupDrawVertices(vertices);
+ } else {
+ void *alphaCoords = ((void*) aaVertices) + gVertexAlphaOffset;
+ // innerProportion is the ratio of the inner (non-AA) port of the line to the total
+ // AA stroke width (the base stroke width expanded by a half pixel on either side).
+ // This value is used in the fragment shader to determine how to fill fragments.
+ float innerProportion = fmax(strokeWidth - 1.0f, 0) / (strokeWidth + .5f);
+ setupDrawAALine((void*) aaVertices, (void*) alphaCoords, innerProportion);
+ }
+
+ int generatedVerticesCount = 0;
+ AlphaVertex *prevAAVertex = NULL;
+ Vertex *prevVertex = NULL;
+ float inverseScaleX, inverseScaleY;
+ if (isHairline) {
+ // The quad that we use for AA hairlines needs to account for scaling because the line
+ // should always be one pixel wide regardless of scale.
+ inverseScaleX = 1.0f;
+ inverseScaleY = 1.0f;
+ if (!mSnapshot->transform->isPureTranslate()) {
+ Matrix4 *mat = mSnapshot->transform;
+ float m00 = mat->data[Matrix4::kScaleX];
+ float m01 = mat->data[Matrix4::kSkewY];
+ float m02 = mat->data[2];
+ float m10 = mat->data[Matrix4::kSkewX];
+ float m11 = mat->data[Matrix4::kScaleX];
+ float m12 = mat->data[6];
+ float scaleX = sqrt(m00*m00 + m01*m01);
+ float scaleY = sqrt(m10*m10 + m11*m11);
+ inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0;
+ inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0;
+ }
+ }
+ for (int i = 0; i < count; i += 4) {
+ // a = start point, b = end point
+ vec2 a(points[i], points[i + 1]);
+ vec2 b(points[i + 2], points[i + 3]);
+
+ // Bias to snap to the same pixels as Skia
+ a += 0.375;
+ b += 0.375;
+
+ // Find the normal to the line
+ vec2 n = (b - a).copyNormalized() * strokeWidth;
+ if (isHairline) {
+ float wideningFactor;
+ if (fabs(n.x) >= fabs(n.y)) {
+ wideningFactor = fabs(1.0f / n.x);
+ } else {
+ wideningFactor = fabs(1.0f / n.y);
+ }
+ n.x *= inverseScaleX;
+ n.y *= inverseScaleY;
+ n *= wideningFactor;
+ }
+ float x = n.x;
+ n.x = -n.y;
+ n.y = x;
+
+ // Four corners of the rectangle defining a thick line
+ vec2 p1 = a - n;
+ vec2 p2 = a + n;
+ vec2 p3 = b + n;
+ vec2 p4 = b - n;
+
+ const float left = fmin(p1.x, fmin(p2.x, fmin(p3.x, p4.x)));
+ const float right = fmax(p1.x, fmax(p2.x, fmax(p3.x, p4.x)));
+ const float top = fmin(p1.y, fmin(p2.y, fmin(p3.y, p4.y)));
+ const float bottom = fmax(p1.y, fmax(p2.y, fmax(p3.y, p4.y)));
+
+ if (!quickReject(left, top, right, bottom)) {
+ // Draw the line as 2 triangles, could be optimized
+ // by using only 4 vertices and the correct indices
+ // Also we should probably used non textured vertices
+ // when line AA is disabled to save on bandwidth
+ if (!isAA) {
+ if (prevVertex != NULL) {
+ // Issue two repeat vertices to create degenerate triangles to bridge
+ // between the previous line and the new one. This is necessary because
+ // we are creating a single triangle_strip which will contain
+ // potentially discontinuous line segments.
+ Vertex::set(vertices++, prevVertex->position[0], prevVertex->position[1]);
+ Vertex::set(vertices++, p1.x, p1.y);
+ generatedVerticesCount += 2;
+ }
+ Vertex::set(vertices++, p1.x, p1.y);
+ Vertex::set(vertices++, p2.x, p2.y);
+ Vertex::set(vertices++, p4.x, p4.y);
+ Vertex::set(vertices++, p3.x, p3.y);
+ prevVertex = vertices - 1;
+ generatedVerticesCount += 4;
+ } else {
+ if (prevAAVertex != NULL) {
+ // Issue two repeat vertices to create degenerate triangles to bridge
+ // between the previous line and the new one. This is necessary because
+ // we are creating a single triangle_strip which will contain
+ // potentially discontinuous line segments.
+ AlphaVertex::set(aaVertices++,prevAAVertex->position[0],
+ prevAAVertex->position[1], prevAAVertex->alpha);
+ AlphaVertex::set(aaVertices++, p4.x, p4.y, 1);
+ generatedVerticesCount += 2;
+ }
+ AlphaVertex::set(aaVertices++, p4.x, p4.y, 1);
+ AlphaVertex::set(aaVertices++, p1.x, p1.y, 1);
+ AlphaVertex::set(aaVertices++, p3.x, p3.y, 0);
+ AlphaVertex::set(aaVertices++, p2.x, p2.y, 0);
+ prevAAVertex = aaVertices - 1;
+ generatedVerticesCount += 4;
+ }
+ dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
+ }
+ }
+ if (generatedVerticesCount > 0) {
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, generatedVerticesCount);
+ }
+}
+
void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) {
if (mSnapshot->isIgnored()) return;
const bool isAA = paint->isAntiAlias();
const float strokeWidth = paint->getStrokeWidth() * 0.5f;
- // A stroke width of 0 has a special meaningin Skia:
- // it draws an unscaled 1px wide line
- const bool isHairLine = paint->getStrokeWidth() == 0.0f;
+ // A stroke width of 0 has a special meaning in Skia:
+ // it draws a line 1 px wide regardless of current transform
+ bool isHairLine = paint->getStrokeWidth() == 0.0f;
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
-
- int verticesCount = count >> 2;
int generatedVerticesCount = 0;
- if (!isHairLine) {
- // TODO: AA needs more vertices
- verticesCount *= 6;
- } else {
- // TODO: AA will be different
- verticesCount *= 2;
- }
-
- TextureVertex lines[verticesCount];
- TextureVertex* vertex = &lines[0];
setupDraw();
+ if (isAA) {
+ setupDrawAALine();
+ }
setupDrawColor(paint->getColor(), alpha);
setupDrawColorFilter();
setupDrawShader();
- setupDrawBlending(mode);
+ if (isAA) {
+ setupDrawBlending(true, mode);
+ } else {
+ setupDrawBlending(mode);
+ }
setupDrawProgram();
setupDrawModelViewIdentity();
setupDrawColorUniforms();
setupDrawColorFilterUniforms();
setupDrawShaderIdentityUniforms();
- setupDrawMesh(vertex);
if (!isHairLine) {
- // TODO: Handle the AA case
- for (int i = 0; i < count; i += 4) {
- // a = start point, b = end point
- vec2 a(points[i], points[i + 1]);
- vec2 b(points[i + 2], points[i + 3]);
-
- // Bias to snap to the same pixels as Skia
- a += 0.375;
- b += 0.375;
-
- // Find the normal to the line
- vec2 n = (b - a).copyNormalized() * strokeWidth;
- float x = n.x;
- n.x = -n.y;
- n.y = x;
-
- // Four corners of the rectangle defining a thick line
- vec2 p1 = a - n;
- vec2 p2 = a + n;
- vec2 p3 = b + n;
- vec2 p4 = b - n;
-
- const float left = fmin(p1.x, fmin(p2.x, fmin(p3.x, p4.x)));
- const float right = fmax(p1.x, fmax(p2.x, fmax(p3.x, p4.x)));
- const float top = fmin(p1.y, fmin(p2.y, fmin(p3.y, p4.y)));
- const float bottom = fmax(p1.y, fmax(p2.y, fmax(p3.y, p4.y)));
-
- if (!quickReject(left, top, right, bottom)) {
- // Draw the line as 2 triangles, could be optimized
- // by using only 4 vertices and the correct indices
- // Also we should probably used non textured vertices
- // when line AA is disabled to save on bandwidth
- TextureVertex::set(vertex++, p1.x, p1.y, 0.0f, 0.0f);
- TextureVertex::set(vertex++, p2.x, p2.y, 0.0f, 0.0f);
- TextureVertex::set(vertex++, p3.x, p3.y, 0.0f, 0.0f);
- TextureVertex::set(vertex++, p1.x, p1.y, 0.0f, 0.0f);
- TextureVertex::set(vertex++, p3.x, p3.y, 0.0f, 0.0f);
- TextureVertex::set(vertex++, p4.x, p4.y, 0.0f, 0.0f);
-
- generatedVerticesCount += 6;
-
- dirtyLayer(left, top, right, bottom, *mSnapshot->transform);
- }
- }
-
- if (generatedVerticesCount > 0) {
- // GL_LINE does not give the result we want to match Skia
- glDrawArrays(GL_TRIANGLES, 0, generatedVerticesCount);
- }
+ drawLinesAsQuads(points, count, isAA, isHairLine, strokeWidth);
} else {
- // TODO: Handle the AA case
- for (int i = 0; i < count; i += 4) {
- TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f);
- TextureVertex::set(vertex++, points[i + 2], points[i + 3], 0.0f, 0.0f);
+ if (isAA) {
+ drawLinesAsQuads(points, count, isAA, isHairLine, .5f);
+ } else {
+ int verticesCount = count >> 1;
+ Vertex lines[verticesCount];
+ Vertex* vertices = &lines[0];
+ setupDrawVertices(vertices);
+ for (int i = 0; i < count; i += 4) {
- generatedVerticesCount += 2;
+ const float left = fmin(points[i], points[i + 2]);
+ const float right = fmax(points[i], points[i + 2]);
+ const float top = fmin(points[i + 1], points[i + 3]);
+ const float bottom = fmax(points[i + 1], points[i + 3]);
- const float left = fmin(points[i], points[i + 2]);
- const float right = fmax(points[i], points[i + 2]);
- const float top = fmin(points[i + 1], points[i + 3]);
- const float bottom = fmax(points[i + 1], points[i + 3]);
+ Vertex::set(vertices++, points[i], points[i + 1]);
+ Vertex::set(vertices++, points[i + 2], points[i + 3]);
+ generatedVerticesCount += 2;
+ dirtyLayer(left, top,
+ right == left ? left + 1 : right, bottom == top ? top + 1 : bottom,
+ *mSnapshot->transform);
+ }
- dirtyLayer(left, top,
- right == left ? left + 1 : right, bottom == top ? top + 1 : bottom,
- *mSnapshot->transform);
+ glLineWidth(1.0f);
+ glDrawArrays(GL_LINES, 0, generatedVerticesCount);
}
-
- glLineWidth(1.0f);
- glDrawArrays(GL_LINES, 0, generatedVerticesCount);
}
}
@@ -1467,7 +1588,7 @@
// TODO: The paint's cap style defines whether the points are square or circular
// TODO: Handle AA for round points
- // A stroke width of 0 has a special meaningin Skia:
+ // A stroke width of 0 has a special meaning in Skia:
// it draws an unscaled 1px point
const bool isHairLine = paint->getStrokeWidth() == 0.0f;
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 4b93b80..0276095 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -283,6 +283,19 @@
void drawAlphaBitmap(Texture* texture, float left, float top, SkPaint* paint);
/**
+ * Draws a line as a quad. Called by drawLines() for all cases except hairline without AA.
+ *
+ * @param points The vertices of the lines. Every four entries specifies the x/y points
+ * of a single line segment.
+ * @param count The number of entries in the points array.
+ * @param isAA Whether the line is anti-aliased
+ * @param isHairline Whether the line has strokeWidth==0, which results in the line being
+ * one pixel wide on the display regardless of scale.
+ */
+ void drawLinesAsQuads(float *points, int count, bool isAA, bool isHairline,
+ float strokeWidth);
+
+ /**
* Draws a textured rectangle with the specified texture. The specified coordinates
* are transformed by the current snapshot's transform matrix.
*
@@ -425,6 +438,7 @@
* Various methods to setup OpenGL rendering.
*/
void setupDrawWithTexture(bool isAlpha8 = false);
+ void setupDrawAALine();
void setupDrawPoint(float pointSize);
void setupDrawColor(int color);
void setupDrawColor(int color, int alpha);
@@ -453,6 +467,8 @@
void setupDrawSimpleMesh();
void setupDrawTexture(GLuint texture);
void setupDrawMesh(GLvoid* vertices, GLvoid* texCoords = NULL, GLuint vbo = 0);
+ void setupDrawVertices(GLvoid* vertices);
+ void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, float strokeWidth);
void finishDrawTexture();
void drawRegionRects(const Region& region);
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index f0bc36b..b873bb8 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -39,6 +39,8 @@
"attribute vec4 position;\n";
const char* gVS_Header_Attributes_TexCoords =
"attribute vec2 texCoords;\n";
+const char* gVS_Header_Attributes_Distance =
+ "attribute float vtxDistance;\n";
const char* gVS_Header_Uniforms =
"uniform mat4 transform;\n";
const char* gVS_Header_Uniforms_IsPoint =
@@ -56,6 +58,8 @@
"uniform mediump vec2 textureDimension;\n";
const char* gVS_Header_Varyings_HasTexture =
"varying vec2 outTexCoords;\n";
+const char* gVS_Header_Varyings_HasWidth =
+ "varying float distance;\n";
const char* gVS_Header_Varyings_HasBitmap =
"varying vec2 outBitmapTexCoords;\n";
const char* gVS_Header_Varyings_PointHasBitmap =
@@ -88,6 +92,8 @@
" gl_Position = transform * position;\n";
const char* gVS_Main_PointSize =
" gl_PointSize = pointSize;\n";
+const char* gVS_Main_Width =
+ " distance = vtxDistance;\n";
const char* gVS_Footer =
"}\n\n";
@@ -101,6 +107,10 @@
"precision mediump float;\n\n";
const char* gFS_Uniforms_Color =
"uniform vec4 color;\n";
+const char* gFS_Uniforms_Width =
+ "uniform float width;\n"
+ "uniform float boundaryWidth;\n"
+ "uniform float inverseBoundaryWidth;\n";
const char* gFS_Header_Uniforms_PointHasBitmap =
"uniform vec2 textureDimension;\n"
"uniform float pointSize;\n";
@@ -169,6 +179,12 @@
// General case
const char* gFS_Main_FetchColor =
" fragColor = color;\n";
+const char* gFS_Main_AccountForWidth =
+ " if (distance < boundaryWidth) {\n"
+ " fragColor *= (distance * inverseBoundaryWidth);\n"
+ " } else if (distance > (1.0 - boundaryWidth)) {\n"
+ " fragColor *= ((1.0 - distance) * inverseBoundaryWidth);\n"
+ " }\n";
const char* gFS_Main_FetchTexture[2] = {
// Don't modulate
" fragColor = texture2D(sampler, outTexCoords);\n",
@@ -354,6 +370,9 @@
if (description.hasTexture) {
shader.append(gVS_Header_Attributes_TexCoords);
}
+ if (description.hasWidth) {
+ shader.append(gVS_Header_Attributes_Distance);
+ }
// Uniforms
shader.append(gVS_Header_Uniforms);
if (description.hasGradient) {
@@ -369,6 +388,9 @@
if (description.hasTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
+ if (description.hasWidth) {
+ shader.append(gVS_Header_Varyings_HasWidth);
+ }
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
}
@@ -383,6 +405,9 @@
if (description.hasTexture) {
shader.append(gVS_Main_OutTexCoords);
}
+ if (description.hasWidth) {
+ shader.append(gVS_Main_Width);
+ }
if (description.hasGradient) {
shader.append(gVS_Main_OutGradient[description.gradientType]);
}
@@ -420,6 +445,9 @@
if (description.hasTexture) {
shader.append(gVS_Header_Varyings_HasTexture);
}
+ if (description.hasWidth) {
+ shader.append(gVS_Header_Varyings_HasWidth);
+ }
if (description.hasGradient) {
shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
}
@@ -441,6 +469,9 @@
if (description.hasTexture) {
shader.append(gFS_Uniforms_TextureSampler);
}
+ if (description.hasWidth) {
+ shader.append(gFS_Uniforms_Width);
+ }
if (description.hasGradient) {
shader.append(gFS_Uniforms_GradientSampler[description.gradientType]);
}
@@ -449,8 +480,8 @@
}
// Optimization for common cases
- if (!blendFramebuffer && description.colorOp == ProgramDescription::kColorNone &&
- !description.isPoint) {
+ if (!description.hasWidth && !blendFramebuffer &&
+ description.colorOp == ProgramDescription::kColorNone && !description.isPoint) {
bool fast = false;
const bool noShader = !description.hasGradient && !description.hasBitmap;
@@ -534,6 +565,9 @@
shader.append(gFS_Main_FetchColor);
}
}
+ if (description.hasWidth) {
+ shader.append(gFS_Main_AccountForWidth);
+ }
if (description.hasGradient) {
shader.append(gFS_Main_FetchGradient[description.gradientType]);
}
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 737d91b..cead75b 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -57,6 +57,7 @@
#define PROGRAM_KEY_COLOR_BLEND 0x80
#define PROGRAM_KEY_BITMAP_NPOT 0x100
#define PROGRAM_KEY_SWAP_SRC_DST 0x2000
+#define PROGRAM_KEY_VERTEX_WIDTH 0x4000
#define PROGRAM_KEY_BITMAP_WRAPS_MASK 0x600
#define PROGRAM_KEY_BITMAP_WRAPT_MASK 0x1800
@@ -119,6 +120,8 @@
bool hasBitmap;
bool isBitmapNpot;
+ bool hasWidth;
+
bool hasGradient;
Gradient gradientType;
@@ -148,6 +151,8 @@
hasTexture = false;
hasAlpha8Texture = false;
+ hasWidth = false;
+
modulate = false;
hasBitmap = false;
@@ -200,6 +205,7 @@
programid key() const {
programid key = 0;
if (hasTexture) key |= PROGRAM_KEY_TEXTURE;
+ if (hasWidth) key |= PROGRAM_KEY_VERTEX_WIDTH;
if (hasAlpha8Texture) key |= PROGRAM_KEY_A8_TEXTURE;
if (hasBitmap) {
key |= PROGRAM_KEY_BITMAP;
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
index bbf4d4a..c120428 100644
--- a/libs/hwui/Vertex.h
+++ b/libs/hwui/Vertex.h
@@ -23,6 +23,18 @@
/**
* Simple structure to describe a vertex with a position and a texture.
*/
+struct Vertex {
+ float position[2];
+
+ static inline void set(Vertex* vertex, float x, float y) {
+ vertex[0].position[0] = x;
+ vertex[0].position[1] = y;
+ }
+}; // struct Vertex
+
+/**
+ * Simple structure to describe a vertex with a position and a texture.
+ */
struct TextureVertex {
float position[2];
float texture[2];
@@ -40,6 +52,22 @@
}
}; // struct TextureVertex
+/**
+ * Simple structure to describe a vertex with a position and an alpha value.
+ */
+struct AlphaVertex : Vertex {
+ float alpha;
+
+ static inline void set(AlphaVertex* vertex, float x, float y, float alpha) {
+ Vertex::set(vertex, x, y);
+ vertex[0].alpha = alpha;
+ }
+
+ static inline void setColor(AlphaVertex* vertex, float alpha) {
+ vertex[0].alpha = alpha;
+ }
+}; // struct AlphaVertex
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/rs/driver/rsdGL.cpp b/libs/rs/driver/rsdGL.cpp
index 6238edd..26e1bdf 100644
--- a/libs/rs/driver/rsdGL.cpp
+++ b/libs/rs/driver/rsdGL.cpp
@@ -197,7 +197,9 @@
LOGE("%p, couldn't find an EGLConfig matching the screen format\n", rsc);
}
//if (props.mLogVisual) {
+ if (0) {
printEGLConfiguration(dc->gl.egl.display, dc->gl.egl.config);
+ }
//}
dc->gl.egl.context = eglCreateContext(dc->gl.egl.display, dc->gl.egl.config,
@@ -281,7 +283,9 @@
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &dc->gl.gl.EXT_texture_max_aniso);
}
- DumpDebug(dc);
+ if (0) {
+ DumpDebug(dc);
+ }
LOGV("initGLThread end %p", rsc);
return true;
diff --git a/libs/ui/Input.cpp b/libs/ui/Input.cpp
index bbe579e..a95f432 100644
--- a/libs/ui/Input.cpp
+++ b/libs/ui/Input.cpp
@@ -832,6 +832,7 @@
const Position& oldestPosition =
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
nsecs_t lastDuration = 0;
+
while (numTouches-- > 1) {
if (++index == HISTORY_SIZE) {
index = 0;
@@ -858,6 +859,14 @@
// Make sure we used at least one sample.
if (samplesUsed != 0) {
+ // Scale the velocity linearly if the window of samples is small.
+ nsecs_t totalDuration = newestMovement.eventTime - oldestMovement.eventTime;
+ if (totalDuration < MIN_WINDOW) {
+ float scale = float(totalDuration) / float(MIN_WINDOW);
+ accumVx *= scale;
+ accumVy *= scale;
+ }
+
*outVx = accumVx;
*outVy = accumVy;
return true;
diff --git a/libs/utils/Looper.cpp b/libs/utils/Looper.cpp
index d5dd126..b54fb9d 100644
--- a/libs/utils/Looper.cpp
+++ b/libs/utils/Looper.cpp
@@ -662,7 +662,8 @@
#endif
void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {
- sendMessageAtTime(LLONG_MIN, handler, message);
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ sendMessageAtTime(now, handler, message);
}
void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 1e4b585..5865b92 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -745,7 +745,6 @@
* @param uri the Content URI of the data you want to play
* @param headers the headers to be sent together with the request for the data
* @throws IllegalStateException if it is called in an invalid state
- * @hide pending API council
*/
public void setDataSource(Context context, Uri uri, Map<String, String> headers)
throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
diff --git a/media/jni/mediaeditor/VideoBrowserMain.c b/media/jni/mediaeditor/VideoBrowserMain.c
index caf4497..cddab60 100755
--- a/media/jni/mediaeditor/VideoBrowserMain.c
+++ b/media/jni/mediaeditor/VideoBrowserMain.c
@@ -159,15 +159,15 @@
VIDEOBROWSER, (M4OSA_Char*)"Video browser context");
CHECK_PTR(videoBrowserCreate, pContext,err, M4ERR_ALLOC);
- M4OSA_memset((M4OSA_MemAddr8)pContext, sizeof(VideoBrowserContext), 0);
+ memset((void *)pContext, 0,sizeof(VideoBrowserContext));
/*--- Initialize the context parameters ---*/
pContext->m_state = VideoBrowser_kVBCreating ;
pContext->m_frameColorType = clrType;
/*--- Copy the file reader functions ---*/
- M4OSA_memcpy((M4OSA_MemAddr8)&pContext->m_fileReadPtr,
- (M4OSA_MemAddr8)ptrF,
+ memcpy((void *)&pContext->m_fileReadPtr,
+ (void *)ptrF,
sizeof(M4OSA_FileReadPointer)) ;
/* PR#SP00013 DGR bug 13 : first frame is not visible */
diff --git a/media/jni/mediaeditor/VideoEditorJava.cpp b/media/jni/mediaeditor/VideoEditorJava.cpp
index 884256a..13f6350 100755
--- a/media/jni/mediaeditor/VideoEditorJava.cpp
+++ b/media/jni/mediaeditor/VideoEditorJava.cpp
@@ -339,7 +339,7 @@
{
// Determine the length of the path
// (add one extra character for the zero terminator).
- length = M4OSA_chrLength(pLocal) + 1;
+ length = strlen((const char *)pLocal) + 1;
// Allocate memory for the string.
pString = videoEditOsal_alloc(pResult, pEnv, length, "String");
diff --git a/media/jni/mediaeditor/VideoEditorMain.cpp b/media/jni/mediaeditor/VideoEditorMain.cpp
index 11e2a5e..b792295 100755
--- a/media/jni/mediaeditor/VideoEditorMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorMain.cpp
@@ -780,7 +780,7 @@
{
M4OSA_Context fileContext;
M4OSA_Char* fileName = (M4OSA_Char*)"/mnt/sdcard/FirstRGB565.raw";
- M4OSA_fileExtraDelete((const M4OSA_Char *)fileName);
+ remove((const char *)fileName);
M4OSA_fileWriteOpen(&fileContext, (M4OSA_Void*) fileName,\
M4OSA_kFileWrite|M4OSA_kFileCreate);
M4OSA_fileWriteData(fileContext, (M4OSA_MemAddr8) pixelArray,
@@ -826,7 +826,7 @@
{
M4OSA_Context fileContext;
M4OSA_Char* fileName = (M4OSA_Char*)"/mnt/sdcard/ConvertedYuv.yuv";
- M4OSA_fileExtraDelete((const M4OSA_Char *)fileName);
+ remove((const char *)fileName);
M4OSA_fileWriteOpen(&fileContext, (M4OSA_Void*) fileName,\
M4OSA_kFileWrite|M4OSA_kFileCreate);
M4OSA_fileWriteData(fileContext,
@@ -1188,19 +1188,19 @@
// generate the path for temp 3gp output file
pTemp3gpFilePath = (M4OSA_Char*) M4OSA_malloc (
- (M4OSA_chrLength((M4OSA_Char*)pContext->initParams.pTempPath)
- + M4OSA_chrLength ((M4OSA_Char*)TEMP_MCS_OUT_FILE_PATH)) + 1 /* for null termination */ , 0x0,
- (M4OSA_Char*) "Malloc for temp 3gp file");
- if ( pTemp3gpFilePath != M4OSA_NULL )
+ (strlen((const char*)pContext->initParams.pTempPath)
+ + strlen((const char*)TEMP_MCS_OUT_FILE_PATH)) + 1 /* for null termination */ , 0x0,
+ (M4OSA_Char*)"Malloc for temp 3gp file");
+ if (pTemp3gpFilePath != M4OSA_NULL)
{
- M4OSA_memset(pTemp3gpFilePath ,
- M4OSA_chrLength((M4OSA_Char*)pContext->initParams.pTempPath)
- + M4OSA_chrLength((M4OSA_Char*)TEMP_MCS_OUT_FILE_PATH) + 1, 0);
- M4OSA_chrNCat ( (M4OSA_Char*)pTemp3gpFilePath,
- (M4OSA_Char*)pContext->initParams.pTempPath ,
- M4OSA_chrLength ((M4OSA_Char*)pContext->initParams.pTempPath));
- M4OSA_chrNCat ( pTemp3gpFilePath , (M4OSA_Char*)TEMP_MCS_OUT_FILE_PATH,
- M4OSA_chrLength ((M4OSA_Char*)TEMP_MCS_OUT_FILE_PATH));
+ memset((void *)pTemp3gpFilePath ,0,
+ strlen((const char*)pContext->initParams.pTempPath)
+ + strlen((const char*)TEMP_MCS_OUT_FILE_PATH) + 1);
+ strncat((char *)pTemp3gpFilePath,
+ (const char *)pContext->initParams.pTempPath ,
+ (size_t) ((M4OSA_Char*)pContext->initParams.pTempPath));
+ strncat((char *)pTemp3gpFilePath , (const char *)TEMP_MCS_OUT_FILE_PATH,
+ (size_t)strlen ((const char*)TEMP_MCS_OUT_FILE_PATH));
}
else {
M4MCS_abort(mcsContext);
@@ -1219,7 +1219,7 @@
pInputFileType = (M4VIDEOEDITING_FileType)pContext->mAudioSettings->fileType;
VIDEOEDIT_LOG_API(ANDROID_LOG_INFO, "VIDEO_EDITOR", "TEMP_MCS_OUT_FILE_PATH len %d",
- M4OSA_chrLength ((M4OSA_Char*)TEMP_MCS_OUT_FILE_PATH));
+ strlen ((const char*)TEMP_MCS_OUT_FILE_PATH));
VIDEOEDIT_LOG_API(ANDROID_LOG_INFO, "VIDEO_EDITOR", "pTemp3gpFilePath %s",
pOutputFile);
@@ -1395,7 +1395,7 @@
(M4NO_ERROR != result), result);
//pContext->mAudioSettings->pFile = pOutputParams->pOutputPCMfile;
- M4OSA_fileExtraDelete((const M4OSA_Char *) pTemp3gpFilePath);
+ remove((const char *) pTemp3gpFilePath);
VIDEOEDIT_LOG_FUNCTION(ANDROID_LOG_INFO, "VIDEO_EDITOR", "videoEditor_generateAudio() EXIT ");
if (pTemp3gpFilePath != M4OSA_NULL) {
@@ -1709,9 +1709,9 @@
goto videoEditor_populateSettings_cleanup;
}
- M4OSA_memcpy((M4OSA_Int8 *)&pContext->pEditSettings->\
+ memcpy((void *)&pContext->pEditSettings->\
Effects[j].xVSS.pFramingBuffer->\
- pac_data[0],(M4OSA_Int8 *)&aFramingCtx->FramingRgb->pac_data[0],(width*height*2));
+ pac_data[0],(void *)&aFramingCtx->FramingRgb->pac_data[0],(width*height*2));
//As of now rgb type is 565
pContext->pEditSettings->Effects[j].xVSS.rgbType =
@@ -1854,8 +1854,8 @@
(M4OSA_UInt32)(strlen((const char*)pTempChar))+1 /* +1 for NULL termination */, 0,
(M4OSA_Char*)"strPath allocation " );
if (pContext->mAudioSettings->pFile != M4OSA_NULL) {
- M4OSA_memcpy((M4OSA_Int8 *)pContext->mAudioSettings->pFile ,
- (M4OSA_Int8 *)pTempChar , strlen((const char*)pTempChar));
+ memcpy((void *)pContext->mAudioSettings->pFile ,
+ (void *)pTempChar , strlen((const char*)pTempChar));
((M4OSA_Int8 *)(pContext->mAudioSettings->pFile))[strlen((const char*)pTempChar)] = '\0';
pEnv->ReleaseStringUTFChars(strPath,(const char *)pTempChar);
} else {
@@ -1879,8 +1879,8 @@
(M4OSA_UInt32)(strlen((const char*)pTempChar))+1 /* +1 for NULL termination */, 0,
(M4OSA_Char*)"strPCMPath allocation " );
if (pContext->mAudioSettings->pPCMFilePath != M4OSA_NULL) {
- M4OSA_memcpy((M4OSA_Int8 *)pContext->mAudioSettings->pPCMFilePath ,
- (M4OSA_Int8 *)pTempChar , strlen((const char*)pTempChar));
+ memcpy((void *)pContext->mAudioSettings->pPCMFilePath ,
+ (void *)pTempChar , strlen((const char*)pTempChar));
((M4OSA_Int8 *)(pContext->mAudioSettings->pPCMFilePath))[strlen((const char*)pTempChar)] = '\0';
pEnv->ReleaseStringUTFChars(strPCMPath,(const char *)pTempChar);
} else {
@@ -2237,7 +2237,7 @@
// Determine the length of the input buffer.
if (M4OSA_NULL != pBufferIn)
{
- length = M4OSA_chrLength((M4OSA_Char *)pBufferIn);
+ length = strlen((const char *)pBufferIn);
}
// Check if the output buffer is large enough to hold the input buffer.
@@ -2282,7 +2282,7 @@
// Determine the length of the input buffer.
if (M4OSA_NULL != pBufferIn)
{
- length = M4OSA_chrLength((M4OSA_Char *)pBufferIn);
+ length = strlen((const char *)pBufferIn);
}
// Check if the output buffer is large enough to hold the input buffer.
@@ -2498,13 +2498,14 @@
(M4OSA_Char *)videoEditJava_getString(&initialized, pEnv, tempPath,
NULL, M4OSA_NULL);
pContext->initParams.pTempPath = (M4OSA_Char *)
- M4OSA_malloc(M4OSA_chrLength(tmpString) + 1, 0x0,
+ M4OSA_malloc(strlen((const char *)tmpString) + 1, 0x0,
(M4OSA_Char *)"tempPath");
//initialize the first char. so that strcat works.
M4OSA_Char *ptmpChar = (M4OSA_Char*)pContext->initParams.pTempPath;
ptmpChar[0] = 0x00;
- M4OSA_chrNCat((M4OSA_Char*)pContext->initParams.pTempPath, tmpString, M4OSA_chrLength(tmpString));
- M4OSA_chrNCat((M4OSA_Char*)pContext->initParams.pTempPath, (M4OSA_Char*)"/", 1);
+ strncat((char *)pContext->initParams.pTempPath, (const char *)tmpString,
+ (size_t)strlen((const char *)tmpString));
+ strncat((char *)pContext->initParams.pTempPath, (const char *)"/", (size_t)1);
M4OSA_free((M4OSA_MemAddr32)tmpString);
pContext->mIsUpdateOverlay = false;
pContext->mOverlayFileName = NULL;
@@ -3286,7 +3287,7 @@
/* loop until EOF */
do
{
- M4OSA_memset((M4OSA_MemAddr8)bufferIn.m_dataAddress,bufferIn.m_bufferSize, 0);
+ memset((void *)bufferIn.m_dataAddress,0,bufferIn.m_bufferSize);
numBytesToRead = samplesCountInBytes;
diff --git a/media/jni/mediaeditor/VideoEditorOsal.cpp b/media/jni/mediaeditor/VideoEditorOsal.cpp
index 035f59a..339c0d1 100755
--- a/media/jni/mediaeditor/VideoEditorOsal.cpp
+++ b/media/jni/mediaeditor/VideoEditorOsal.cpp
@@ -280,7 +280,7 @@
if (M4OSA_NULL != pData)
{
// Reset the allocated memory.
- M4OSA_memset((M4OSA_MemAddr8)pData, size, 0);
+ memset((void *)pData, 0,size);
#ifdef OSAL_MEM_LEAK_DEBUG
// Update the allocated block count.
gAllocatedBlockCount++;
diff --git a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
index 3b795ce..39221f3 100755
--- a/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorPropertiesMain.cpp
@@ -147,8 +147,8 @@
if (gotten)
{
// Retrieve the extension.
- result = M4OSA_chrReverseFindChar(pFile, '.', &pExtension);
- if ((M4NO_ERROR == result) && (M4OSA_NULL != pExtension))
+ pExtension = (M4OSA_Char *)strrchr((const char *)pFile, (int)'.');
+ if (M4OSA_NULL != pExtension)
{
// Skip the dot.
pExtension++;
@@ -341,7 +341,7 @@
M4OSA_UInt32 index = 0;
M4OSA_ERR result = M4NO_ERROR;
M4OSA_Int32 cmpResult = 0;
- M4OSA_UInt32 extLength = M4OSA_chrLength(pExtension);
+ M4OSA_UInt32 extLength = strlen((const char *)pExtension);
// Assign default
*pFileType = VideoEditClasses_kFileType_Unsupported;
@@ -353,7 +353,7 @@
// Convert the extension to lowercase.
for (index = 0; index < extLength ; index++)
{
- extension[index] = M4OSA_chrToLower(pExtension[index]);
+ extension[index] = tolower((int)pExtension[index]);
}
// Check if the extension is ".mp3".
@@ -539,7 +539,7 @@
M4OSA_Char* pStrIn2,
M4OSA_Int32* pCmpResult)
{
- M4OSA_chrCompare(pStrIn1, pStrIn2, pCmpResult);
+ *pCmpResult = strcmp((const char *)pStrIn1, (const char *)pStrIn2);
return *pCmpResult;
}
diff --git a/media/jni/mediaeditor/VideoEditorThumbnailMain.cpp b/media/jni/mediaeditor/VideoEditorThumbnailMain.cpp
index b1f9fe4..461bdd0 100755
--- a/media/jni/mediaeditor/VideoEditorThumbnailMain.cpp
+++ b/media/jni/mediaeditor/VideoEditorThumbnailMain.cpp
@@ -134,7 +134,7 @@
for (j = 0; j < pPlane->u_height; j++)
{
- M4OSA_memcpy((M4OSA_MemAddr8 )dst, (M4OSA_MemAddr8 )src, pPlane->u_stride);
+ memcpy((void * )dst, (void * )src, pPlane->u_stride);
for (i = pPlane->u_width; i < pC->m_width; i++)
{
dst[i] = 0;
@@ -170,7 +170,7 @@
M4OSA_TRACE3_1("context value is = %d",pContext);
CHECK_PTR(ThumbnailOpen, pContext, err, M4ERR_ALLOC);
- M4OSA_memset((M4OSA_MemAddr8)pContext, sizeof(ThumbnailContext), 0);
+ memset((void *)pContext, 0,sizeof(ThumbnailContext));
M4OSA_FileReadPointer optFP;
M4OSA_FileReadPointer llFP;
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
index d07ea1b..576a850 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.cpp
@@ -33,11 +33,25 @@
namespace android {
-NuPlayer::HTTPLiveSource::HTTPLiveSource(const char *url, uint32_t flags)
+NuPlayer::HTTPLiveSource::HTTPLiveSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers)
: mURL(url),
- mFlags(flags),
+ mFlags(0),
mEOS(false),
mOffset(0) {
+ if (headers) {
+ mExtraHeaders = *headers;
+
+ ssize_t index =
+ mExtraHeaders.indexOfKey(String8("x-hide-urls-from-log"));
+
+ if (index >= 0) {
+ mFlags |= kFlagIncognito;
+
+ mExtraHeaders.removeItemsAt(index);
+ }
+ }
}
NuPlayer::HTTPLiveSource::~HTTPLiveSource() {
@@ -55,7 +69,8 @@
mLiveLooper->registerHandler(mLiveSession);
- mLiveSession->connect(mURL.c_str());
+ mLiveSession->connect(
+ mURL.c_str(), mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
mTSParser = new ATSParser;
}
diff --git a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
index a8ce7f4..7a337e9 100644
--- a/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
+++ b/media/libmediaplayerservice/nuplayer/HTTPLiveSource.h
@@ -27,11 +27,9 @@
struct LiveSession;
struct NuPlayer::HTTPLiveSource : public NuPlayer::Source {
- enum Flags {
- // Don't log any URLs.
- kFlagIncognito = 1,
- };
- HTTPLiveSource(const char *url, uint32_t flags = 0);
+ HTTPLiveSource(
+ const char *url,
+ const KeyedVector<String8, String8> *headers);
virtual void start();
@@ -49,7 +47,13 @@
virtual ~HTTPLiveSource();
private:
+ enum Flags {
+ // Don't log any URLs.
+ kFlagIncognito = 1,
+ };
+
AString mURL;
+ KeyedVector<String8, String8> mExtraHeaders;
uint32_t mFlags;
bool mEOS;
off64_t mOffset;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index d439f6e..effa703 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -72,17 +72,7 @@
const char *url, const KeyedVector<String8, String8> *headers) {
sp<AMessage> msg = new AMessage(kWhatSetDataSource, id());
- uint32_t flags = 0;
-
- if (headers) {
- ssize_t index = headers->indexOfKey(String8("x-hide-urls-from-log"));
-
- if (index >= 0) {
- flags |= HTTPLiveSource::kFlagIncognito;
- }
- }
-
- msg->setObject("source", new HTTPLiveSource(url, flags));
+ msg->setObject("source", new HTTPLiveSource(url, headers));
msg->post();
}
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
index 1ca2d6d..f9db1a1 100644
--- a/media/libstagefright/MPEG4Extractor.cpp
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -377,7 +377,7 @@
mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
}
- mInitCheck = verifyIfStreamable();
+ mInitCheck = OK;
} else {
mInitCheck = err;
}
@@ -1904,7 +1904,7 @@
off64_t offset;
size_t size;
- uint32_t dts;
+ uint32_t cts;
bool isSyncSample;
bool newBuffer = false;
if (mBuffer == NULL) {
@@ -1912,7 +1912,7 @@
status_t err =
mSampleTable->getMetaDataForSample(
- mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample);
+ mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample);
if (err != OK) {
return err;
@@ -1942,7 +1942,7 @@
mBuffer->set_range(0, size);
mBuffer->meta_data()->clear();
mBuffer->meta_data()->setInt64(
- kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
if (targetSampleTimeUs >= 0) {
mBuffer->meta_data()->setInt64(
@@ -2060,7 +2060,7 @@
mBuffer->meta_data()->clear();
mBuffer->meta_data()->setInt64(
- kKeyTime, ((int64_t)dts * 1000000) / mTimescale);
+ kKeyTime, ((int64_t)cts * 1000000) / mTimescale);
if (targetSampleTimeUs >= 0) {
mBuffer->meta_data()->setInt64(
@@ -2094,87 +2094,6 @@
return NULL;
}
-status_t MPEG4Extractor::verifyIfStreamable() {
- if (!(mDataSource->flags() & DataSource::kIsCachingDataSource)) {
- return OK;
- }
-
- Track *audio = findTrackByMimePrefix("audio/");
- Track *video = findTrackByMimePrefix("video/");
-
- if (audio == NULL || video == NULL) {
- return OK;
- }
-
- sp<SampleTable> audioSamples = audio->sampleTable;
- sp<SampleTable> videoSamples = video->sampleTable;
-
- off64_t maxOffsetDiff = 0;
- int64_t maxOffsetTimeUs = -1;
-
- for (uint32_t i = 0; i < videoSamples->countSamples(); ++i) {
- off64_t videoOffset;
- uint32_t videoTime;
- bool isSync;
- CHECK_EQ((status_t)OK, videoSamples->getMetaDataForSample(
- i, &videoOffset, NULL, &videoTime, &isSync));
-
- int64_t videoTimeUs = (int64_t)(videoTime * 1E6 / video->timescale);
-
- uint32_t reqAudioTime = (videoTimeUs * audio->timescale) / 1000000;
- uint32_t j;
- if (audioSamples->findSampleAtTime(
- reqAudioTime, &j, SampleTable::kFlagClosest) != OK) {
- continue;
- }
-
- off64_t audioOffset;
- uint32_t audioTime;
- CHECK_EQ((status_t)OK, audioSamples->getMetaDataForSample(
- j, &audioOffset, NULL, &audioTime));
-
- int64_t audioTimeUs = (int64_t)(audioTime * 1E6 / audio->timescale);
-
- off64_t offsetDiff = videoOffset - audioOffset;
- if (offsetDiff < 0) {
- offsetDiff = -offsetDiff;
- }
-
-#if 0
- printf("%s%d/%d videoTime %.2f secs audioTime %.2f secs "
- "videoOffset %lld audioOffset %lld offsetDiff %lld\n",
- isSync ? "*" : " ",
- i,
- j,
- videoTimeUs / 1E6,
- audioTimeUs / 1E6,
- videoOffset,
- audioOffset,
- offsetDiff);
-#endif
-
- if (offsetDiff > maxOffsetDiff) {
- maxOffsetDiff = offsetDiff;
- maxOffsetTimeUs = videoTimeUs;
- }
- }
-
-#if 0
- printf("max offset diff: %lld at video time: %.2f secs\n",
- maxOffsetDiff, maxOffsetTimeUs / 1E6);
-#endif
-
- if (maxOffsetDiff < 1024 * 1024) {
- return OK;
- }
-
- LOGE("This content is not streamable, "
- "max offset diff: %lld at video time: %.2f secs",
- maxOffsetDiff, maxOffsetTimeUs / 1E6);
-
- return ERROR_UNSUPPORTED;
-}
-
static bool LegacySniffMPEG4(
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
uint8_t header[8];
diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp
index c1aa46e..dc86885 100644
--- a/media/libstagefright/NuCachedSource2.cpp
+++ b/media/libstagefright/NuCachedSource2.cpp
@@ -323,25 +323,28 @@
}
void NuCachedSource2::restartPrefetcherIfNecessary_l(
- bool ignoreLowWaterThreshold) {
+ bool ignoreLowWaterThreshold, bool force) {
static const size_t kGrayArea = 1024 * 1024;
if (mFetching || mFinalStatus != OK) {
return;
}
- if (!ignoreLowWaterThreshold
+ if (!ignoreLowWaterThreshold && !force
&& mCacheOffset + mCache->totalSize() - mLastAccessPos
>= kLowWaterThreshold) {
return;
}
size_t maxBytes = mLastAccessPos - mCacheOffset;
- if (maxBytes < kGrayArea) {
- return;
- }
- maxBytes -= kGrayArea;
+ if (!force) {
+ if (maxBytes < kGrayArea) {
+ return;
+ }
+
+ maxBytes -= kGrayArea;
+ }
size_t actualBytes = mCache->releaseFromStart(maxBytes);
mCacheOffset += actualBytes;
@@ -413,10 +416,19 @@
}
ssize_t NuCachedSource2::readInternal(off64_t offset, void *data, size_t size) {
+ CHECK_LE(size, (size_t)kHighWaterThreshold);
+
LOGV("readInternal offset %lld size %d", offset, size);
Mutex::Autolock autoLock(mLock);
+ if (!mFetching) {
+ mLastAccessPos = offset;
+ restartPrefetcherIfNecessary_l(
+ false, // ignoreLowWaterThreshold
+ true); // force
+ }
+
if (offset < mCacheOffset
|| offset >= (off64_t)(mCacheOffset + mCache->totalSize())) {
static const off64_t kPadding = 256 * 1024;
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
index 423df70..08db902 100644
--- a/media/libstagefright/SampleTable.cpp
+++ b/media/libstagefright/SampleTable.cpp
@@ -53,6 +53,7 @@
mNumSampleSizes(0),
mTimeToSampleCount(0),
mTimeToSample(NULL),
+ mSampleTimeEntries(NULL),
mCompositionTimeDeltaEntries(NULL),
mNumCompositionTimeDeltaEntries(0),
mSyncSampleOffset(-1),
@@ -73,6 +74,9 @@
delete[] mCompositionTimeDeltaEntries;
mCompositionTimeDeltaEntries = NULL;
+ delete[] mSampleTimeEntries;
+ mSampleTimeEntries = NULL;
+
delete[] mTimeToSample;
mTimeToSample = NULL;
@@ -381,67 +385,128 @@
return time1 > time2 ? time1 - time2 : time2 - time1;
}
-status_t SampleTable::findSampleAtTime(
- uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
- // XXX this currently uses decoding time, instead of composition time.
+// static
+int SampleTable::CompareIncreasingTime(const void *_a, const void *_b) {
+ const SampleTimeEntry *a = (const SampleTimeEntry *)_a;
+ const SampleTimeEntry *b = (const SampleTimeEntry *)_b;
- *sample_index = 0;
+ if (a->mCompositionTime < b->mCompositionTime) {
+ return -1;
+ } else if (a->mCompositionTime > b->mCompositionTime) {
+ return 1;
+ }
+ return 0;
+}
+
+void SampleTable::buildSampleEntriesTable() {
Mutex::Autolock autoLock(mLock);
- uint32_t cur_sample = 0;
- uint32_t time = 0;
+ if (mSampleTimeEntries != NULL) {
+ return;
+ }
+
+ mSampleTimeEntries = new SampleTimeEntry[mNumSampleSizes];
+
+ uint32_t sampleIndex = 0;
+ uint32_t sampleTime = 0;
+
for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
uint32_t n = mTimeToSample[2 * i];
uint32_t delta = mTimeToSample[2 * i + 1];
- if (req_time < time + n * delta) {
- int j = (req_time - time) / delta;
+ for (uint32_t j = 0; j < n; ++j) {
+ CHECK(sampleIndex < mNumSampleSizes);
- uint32_t time1 = time + j * delta;
- uint32_t time2 = time1 + delta;
+ mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex;
- uint32_t sampleTime;
- if (i+1 == mTimeToSampleCount
- || (abs_difference(req_time, time1)
- < abs_difference(req_time, time2))) {
- *sample_index = cur_sample + j;
- sampleTime = time1;
- } else {
- *sample_index = cur_sample + j + 1;
- sampleTime = time2;
- }
+ mSampleTimeEntries[sampleIndex].mCompositionTime =
+ sampleTime + getCompositionTimeOffset(sampleIndex);
- switch (flags) {
- case kFlagBefore:
- {
- if (sampleTime > req_time && *sample_index > 0) {
- --*sample_index;
- }
- break;
- }
-
- case kFlagAfter:
- {
- if (sampleTime < req_time
- && *sample_index + 1 < mNumSampleSizes) {
- ++*sample_index;
- }
- break;
- }
-
- default:
- break;
- }
-
- return OK;
+ ++sampleIndex;
+ sampleTime += delta;
}
-
- time += delta * n;
- cur_sample += n;
}
- return ERROR_OUT_OF_RANGE;
+ qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry),
+ CompareIncreasingTime);
+}
+
+status_t SampleTable::findSampleAtTime(
+ uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
+ buildSampleEntriesTable();
+
+ uint32_t left = 0;
+ uint32_t right = mNumSampleSizes;
+ while (left < right) {
+ uint32_t center = (left + right) / 2;
+ uint32_t centerTime = mSampleTimeEntries[center].mCompositionTime;
+
+ if (req_time < centerTime) {
+ right = center;
+ } else if (req_time > centerTime) {
+ left = center + 1;
+ } else {
+ left = center;
+ break;
+ }
+ }
+
+ if (left == mNumSampleSizes) {
+ --left;
+ }
+
+ uint32_t closestIndex = left;
+
+ switch (flags) {
+ case kFlagBefore:
+ {
+ while (closestIndex > 0
+ && mSampleTimeEntries[closestIndex].mCompositionTime
+ > req_time) {
+ --closestIndex;
+ }
+ break;
+ }
+
+ case kFlagAfter:
+ {
+ while (closestIndex + 1 < mNumSampleSizes
+ && mSampleTimeEntries[closestIndex].mCompositionTime
+ < req_time) {
+ ++closestIndex;
+ }
+ break;
+ }
+
+ default:
+ {
+ CHECK(flags == kFlagClosest);
+
+ if (closestIndex > 0) {
+ // Check left neighbour and pick closest.
+ uint32_t absdiff1 =
+ abs_difference(
+ mSampleTimeEntries[closestIndex].mCompositionTime,
+ req_time);
+
+ uint32_t absdiff2 =
+ abs_difference(
+ mSampleTimeEntries[closestIndex - 1].mCompositionTime,
+ req_time);
+
+ if (absdiff1 > absdiff2) {
+ closestIndex = closestIndex - 1;
+ }
+ }
+
+ break;
+ }
+ }
+
+ *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex;
+
+ return OK;
}
status_t SampleTable::findSyncSampleNear(
@@ -613,7 +678,7 @@
uint32_t sampleIndex,
off64_t *offset,
size_t *size,
- uint32_t *decodingTime,
+ uint32_t *compositionTime,
bool *isSyncSample) {
Mutex::Autolock autoLock(mLock);
@@ -630,8 +695,8 @@
*size = mSampleIterator->getSampleSize();
}
- if (decodingTime) {
- *decodingTime = mSampleIterator->getSampleTime();
+ if (compositionTime) {
+ *compositionTime = mSampleIterator->getSampleTime();
}
if (isSyncSample) {
diff --git a/media/libstagefright/httplive/LiveSession.cpp b/media/libstagefright/httplive/LiveSession.cpp
index 8e1bdf3..012d9ad 100644
--- a/media/libstagefright/httplive/LiveSession.cpp
+++ b/media/libstagefright/httplive/LiveSession.cpp
@@ -67,9 +67,17 @@
return mDataSource;
}
-void LiveSession::connect(const char *url) {
+void LiveSession::connect(
+ const char *url, const KeyedVector<String8, String8> *headers) {
sp<AMessage> msg = new AMessage(kWhatConnect, id());
msg->setString("url", url);
+
+ if (headers != NULL) {
+ msg->setPointer(
+ "headers",
+ new KeyedVector<String8, String8>(*headers));
+ }
+
msg->post();
}
@@ -144,6 +152,16 @@
AString url;
CHECK(msg->findString("url", &url));
+ KeyedVector<String8, String8> *headers = NULL;
+ if (!msg->findPointer("headers", (void **)&headers)) {
+ mExtraHeaders.clear();
+ } else {
+ mExtraHeaders = *headers;
+
+ delete headers;
+ headers = NULL;
+ }
+
if (!(mFlags & kFlagIncognito)) {
LOGI("onConnect '%s'", url.c_str());
} else {
@@ -210,7 +228,8 @@
}
}
- status_t err = mHTTPDataSource->connect(url);
+ status_t err = mHTTPDataSource->connect(
+ url, mExtraHeaders.isEmpty() ? NULL : &mExtraHeaders);
if (err != OK) {
return err;
diff --git a/media/libstagefright/include/LiveSession.h b/media/libstagefright/include/LiveSession.h
index 2b5ea0e..99abe64 100644
--- a/media/libstagefright/include/LiveSession.h
+++ b/media/libstagefright/include/LiveSession.h
@@ -20,6 +20,8 @@
#include <media/stagefright/foundation/AHandler.h>
+#include <utils/String8.h>
+
namespace android {
struct ABuffer;
@@ -37,7 +39,10 @@
sp<DataSource> getDataSource();
- void connect(const char *url);
+ void connect(
+ const char *url,
+ const KeyedVector<String8, String8> *headers = NULL);
+
void disconnect();
// Blocks until seek is complete.
@@ -78,6 +83,8 @@
sp<HTTPBase> mHTTPDataSource;
AString mMasterURL;
+ KeyedVector<String8, String8> mExtraHeaders;
+
Vector<BandwidthItem> mBandwidthItems;
KeyedVector<AString, sp<ABuffer> > mAESKeyForURI;
diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h
index d9ef208..3bd4c7e 100644
--- a/media/libstagefright/include/MPEG4Extractor.h
+++ b/media/libstagefright/include/MPEG4Extractor.h
@@ -92,8 +92,6 @@
Track *findTrackByMimePrefix(const char *mimePrefix);
- status_t verifyIfStreamable();
-
MPEG4Extractor(const MPEG4Extractor &);
MPEG4Extractor &operator=(const MPEG4Extractor &);
};
diff --git a/media/libstagefright/include/NuCachedSource2.h b/media/libstagefright/include/NuCachedSource2.h
index 2128682..ed3e265c 100644
--- a/media/libstagefright/include/NuCachedSource2.h
+++ b/media/libstagefright/include/NuCachedSource2.h
@@ -96,7 +96,9 @@
status_t seekInternal_l(off64_t offset);
size_t approxDataRemaining_l(status_t *finalStatus);
- void restartPrefetcherIfNecessary_l(bool ignoreLowWaterThreshold = false);
+
+ void restartPrefetcherIfNecessary_l(
+ bool ignoreLowWaterThreshold = false, bool force = false);
DISALLOW_EVIL_CONSTRUCTORS(NuCachedSource2);
};
diff --git a/media/libstagefright/include/SampleTable.h b/media/libstagefright/include/SampleTable.h
index 2f95de9..f44e0a2 100644
--- a/media/libstagefright/include/SampleTable.h
+++ b/media/libstagefright/include/SampleTable.h
@@ -63,7 +63,7 @@
uint32_t sampleIndex,
off64_t *offset,
size_t *size,
- uint32_t *decodingTime,
+ uint32_t *compositionTime,
bool *isSyncSample = NULL);
enum {
@@ -107,6 +107,12 @@
uint32_t mTimeToSampleCount;
uint32_t *mTimeToSample;
+ struct SampleTimeEntry {
+ uint32_t mSampleIndex;
+ uint32_t mCompositionTime;
+ };
+ SampleTimeEntry *mSampleTimeEntries;
+
uint32_t *mCompositionTimeDeltaEntries;
size_t mNumCompositionTimeDeltaEntries;
@@ -130,6 +136,10 @@
uint32_t getCompositionTimeOffset(uint32_t sampleIndex) const;
+ static int CompareIncreasingTime(const void *, const void *);
+
+ void buildSampleEntriesTable();
+
SampleTable(const SampleTable &);
SampleTable &operator=(const SampleTable &);
};
diff --git a/obex/javax/obex/ClientSession.java b/obex/javax/obex/ClientSession.java
index 0935383..27d8976 100644
--- a/obex/javax/obex/ClientSession.java
+++ b/obex/javax/obex/ClientSession.java
@@ -449,8 +449,8 @@
maxPacketSize = (mInput.read() << 8) + mInput.read();
//check with local max size
- if (maxPacketSize > ObexHelper.MAX_PACKET_SIZE_INT) {
- maxPacketSize = ObexHelper.MAX_PACKET_SIZE_INT;
+ if (maxPacketSize > ObexHelper.MAX_CLIENT_PACKET_SIZE) {
+ maxPacketSize = ObexHelper.MAX_CLIENT_PACKET_SIZE;
}
if (length > 7) {
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index df0e0fb..8c12a20 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -70,6 +70,12 @@
*/
public static final int MAX_PACKET_SIZE_INT = 0xFFFE;
+ /**
+ * Temporary workaround to be able to push files to Windows 7.
+ * TODO: Should be removed as soon as Microsoft updates their driver.
+ */
+ public static final int MAX_CLIENT_PACKET_SIZE = 0xFC00;
+
public static final int OBEX_OPCODE_CONNECT = 0x80;
public static final int OBEX_OPCODE_DISCONNECT = 0x81;
diff --git a/opengl/java/android/opengl/Matrix.java b/opengl/java/android/opengl/Matrix.java
index b9fd4ab..a412a43 100644
--- a/opengl/java/android/opengl/Matrix.java
+++ b/opengl/java/android/opengl/Matrix.java
@@ -16,8 +16,6 @@
package android.opengl;
-import javax.microedition.khronos.opengles.GL10;
-
/**
* Matrix math utilities. These methods operate on OpenGL ES format
* matrices and vectors stored in float arrays.
@@ -332,6 +330,43 @@
}
/**
+ * Define a projection matrix in terms of a field of view angle, an
+ * aspect ratio, and z clip planes
+ * @param m the float array that holds the perspective matrix
+ * @param offset the offset into float array m where the perspective
+ * matrix data is written
+ * @param fovy field of view in y direction, in degrees
+ * @param aspect width to height aspect ratio of the viewport
+ * @param zNear
+ * @param zFar
+ */
+ public static void perspectiveM(float[] m, int offset,
+ float fovy, float aspect, float zNear, float zFar) {
+ float f = 1.0f / (float) Math.tan(fovy * (Math.PI / 360.0));
+ float rangeReciprocal = 1.0f / (zNear - zFar);
+
+ m[offset + 0] = f / aspect;
+ m[offset + 1] = 0.0f;
+ m[offset + 2] = 0.0f;
+ m[offset + 3] = 0.0f;
+
+ m[offset + 4] = 0.0f;
+ m[offset + 5] = f;
+ m[offset + 6] = 0.0f;
+ m[offset + 7] = 0.0f;
+
+ m[offset + 8] = 0.0f;
+ m[offset + 9] = 0.0f;
+ m[offset + 10] = (zFar + zNear) * rangeReciprocal;
+ m[offset + 11] = -1.0f;
+
+ m[offset + 12] = 0.0f;
+ m[offset + 13] = 0.0f;
+ m[offset + 14] = 2.0f * zFar * rangeReciprocal;
+ m[offset + 15] = 0.0f;
+ }
+
+ /**
* Computes the length of a vector
*
* @param x x coordinate of a vector
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 7d72729..0747efb 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -13,8 +13,8 @@
EGL/hooks.cpp \
EGL/Loader.cpp \
#
-LOCAL_STATIC_LIBRARIES += libGLESv2_dbg libprotobuf-cpp-2.3.0-lite liblzf
-LOCAL_SHARED_LIBRARIES += libcutils libutils libstlport
+
+LOCAL_SHARED_LIBRARIES += libcutils libutils libGLESv2_dbg
LOCAL_LDLIBS := -lpthread -ldl
LOCAL_MODULE:= libEGL
LOCAL_LDFLAGS += -Wl,--exclude-libs=ALL
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 6474c87..9cf7223 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -46,6 +46,7 @@
#include "egl_impl.h"
#include "Loader.h"
#include "glesv2dbg.h"
+#include "egl_tls.h"
#define setError(_e, _r) setErrorEtc(__FUNCTION__, __LINE__, _e, _r)
@@ -58,7 +59,7 @@
static char const * const gVendorString = "Android";
static char const * const gVersionString = "1.4 Android META-EGL";
static char const * const gClientApiString = "OpenGL ES";
-static char const * const gExtensionString =
+static char const * const gExtensionString =
"EGL_KHR_image "
"EGL_KHR_image_base "
"EGL_KHR_image_pixmap "
@@ -221,18 +222,15 @@
struct egl_context_t : public egl_object_t
{
typedef egl_object_t::LocalRef<egl_context_t, EGLContext> Ref;
-
+
egl_context_t(EGLDisplay dpy, EGLContext context, EGLConfig config,
- int impl, egl_connection_t const* cnx, int version)
- : egl_object_t(dpy), dpy(dpy), context(context), config(config), read(0), draw(0),
- impl(impl), cnx(cnx), version(version), dbg(NULL)
+ int impl, egl_connection_t const* cnx, int version)
+ : egl_object_t(dpy), dpy(dpy), context(context), config(config), read(0), draw(0),
+ impl(impl), cnx(cnx), version(version)
{
}
~egl_context_t()
{
- if (dbg)
- DestroyDbgContext(dbg);
- dbg = NULL;
}
EGLDisplay dpy;
EGLContext context;
@@ -242,7 +240,6 @@
int impl;
egl_connection_t const* cnx;
int version;
- DbgContext * dbg;
};
struct egl_image_t : public egl_object_t
@@ -277,15 +274,6 @@
typedef egl_image_t::Ref ImageRef;
typedef egl_sync_t::Ref SyncRef;
-struct tls_t
-{
- tls_t() : error(EGL_SUCCESS), ctx(0), logCallWithNoContext(EGL_TRUE) { }
- EGLint error;
- EGLContext ctx;
- EGLBoolean logCallWithNoContext;
-};
-
-
// ----------------------------------------------------------------------------
static egl_connection_t gEGLImpl[IMPL_NUM_IMPLEMENTATIONS];
@@ -323,7 +311,7 @@
int propertyLevel = atoi(value);
int applicationLevel = gEGLApplicationTraceLevel;
gEGLTraceLevel = propertyLevel > applicationLevel ? propertyLevel : applicationLevel;
-
+
property_get("debug.egl.debug_proc", value, "");
long pid = getpid();
char procPath[128] = {};
@@ -336,14 +324,20 @@
{
if (!strcmp(value, cmdline))
gEGLDebugLevel = 1;
- }
+ }
fclose(file);
}
-
+
if (gEGLDebugLevel > 0)
{
property_get("debug.egl.debug_port", value, "5039");
- StartDebugServer(atoi(value));
+ const unsigned short port = (unsigned short)atoi(value);
+ property_get("debug.egl.debug_forceUseFile", value, "0");
+ const bool forceUseFile = (bool)atoi(value);
+ property_get("debug.egl.debug_maxFileSize", value, "8");
+ const unsigned int maxFileSize = atoi(value) << 20;
+ property_get("debug.egl.debug_filePath", value, "/data/local/tmp/dump.gles2dbg");
+ StartDebugServer(port, forceUseFile, maxFileSize, value);
}
}
@@ -586,7 +580,7 @@
}
static inline
-egl_surface_t* get_surface(EGLSurface surface) {
+egl_surface_t* get_surface(EGLSurface surface) {
return egl_to_native_cast<egl_surface_t>(surface);
}
@@ -595,11 +589,6 @@
return egl_to_native_cast<egl_context_t>(context);
}
-DbgContext * getDbgContextThreadSpecific()
-{
- return get_context(getContext())->dbg;
-}
-
static inline
egl_image_t* get_image(EGLImageKHR image) {
return egl_to_native_cast<egl_image_t>(image);
@@ -1442,10 +1431,12 @@
loseCurrent(cur_c);
if (ctx != EGL_NO_CONTEXT) {
- if (!c->dbg && gEGLDebugLevel > 0)
- c->dbg = CreateDbgContext(c->version, c->cnx->hooks[c->version]);
setGLHooksThreadSpecific(c->cnx->hooks[c->version]);
setContext(ctx);
+ tls_t * const tls = getTLS();
+ if (!tls->dbg && gEGLDebugLevel > 0)
+ tls->dbg = CreateDbgContext(gEGLThreadLocalStorageKey, c->version,
+ c->cnx->hooks[c->version]);
_c.acquire();
_r.acquire();
_d.acquire();
diff --git a/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk
index fc40799..9f6e68c 100644
--- a/opengl/libs/GLES2_dbg/Android.mk
+++ b/opengl/libs/GLES2_dbg/Android.mk
@@ -11,7 +11,7 @@
src/server.cpp \
src/vertex.cpp
-LOCAL_C_INCLUDES := \
+LOCAL_C_INCLUDES := \
$(LOCAL_PATH) \
$(LOCAL_PATH)/../ \
external/stlport/stlport \
@@ -21,7 +21,8 @@
#LOCAL_CFLAGS += -O0 -g -DDEBUG -UNDEBUG
LOCAL_CFLAGS := -DGOOGLE_PROTOBUF_NO_RTTI
-
+LOCAL_STATIC_LIBRARIES := libprotobuf-cpp-2.3.0-lite liblzf
+LOCAL_SHARED_LIBRARIES := libcutils libutils libstlport
ifeq ($(TARGET_ARCH),arm)
LOCAL_CFLAGS += -fstrict-aliasing
endif
@@ -43,4 +44,6 @@
LOCAL_MODULE:= libGLESv2_dbg
LOCAL_MODULE_TAGS := optional
-include $(BUILD_STATIC_LIBRARY)
+include $(BUILD_SHARED_LIBRARY)
+
+include $(LOCAL_PATH)/test/Android.mk
diff --git a/opengl/libs/GLES2_dbg/generate_api_cpp.py b/opengl/libs/GLES2_dbg/generate_api_cpp.py
index 66c110f..aeba213 100755
--- a/opengl/libs/GLES2_dbg/generate_api_cpp.py
+++ b/opengl/libs/GLES2_dbg/generate_api_cpp.py
@@ -26,36 +26,36 @@
return line.replace(annotation, "*")
else:
return line
-
+
def generate_api(lines):
externs = []
i = 0
# these have been hand written
- skipFunctions = ["glReadPixels", "glDrawArrays", "glDrawElements"]
-
+ skipFunctions = ["glDrawArrays", "glDrawElements"]
+
# these have an EXTEND_Debug_* macro for getting data
- extendFunctions = ["glCopyTexImage2D", "glCopyTexSubImage2D", "glShaderSource",
-"glTexImage2D", "glTexSubImage2D"]
-
+ extendFunctions = ["glCopyTexImage2D", "glCopyTexSubImage2D", "glReadPixels",
+"glShaderSource", "glTexImage2D", "glTexSubImage2D"]
+
# these also needs to be forwarded to DbgContext
contextFunctions = ["glUseProgram", "glEnableVertexAttribArray", "glDisableVertexAttribArray",
"glVertexAttribPointer", "glBindBuffer", "glBufferData", "glBufferSubData", "glDeleteBuffers",]
-
+
for line in lines:
if line.find("API_ENTRY(") >= 0: # a function prototype
returnType = line[0: line.find(" API_ENTRY(")]
functionName = line[line.find("(") + 1: line.find(")")] #extract GL function name
parameterList = line[line.find(")(") + 2: line.find(") {")]
-
+
#if line.find("*") >= 0:
# extern = "%s Debug_%s(%s);" % (returnType, functionName, parameterList)
# externs.append(extern)
# continue
-
+
if functionName in skipFunctions:
sys.stderr.write("!\n! skipping function '%s'\n!\n" % (functionName))
continue
-
+
parameters = parameterList.split(',')
paramIndex = 0
if line.find("*") >= 0 and (line.find("*") < line.find(":") or line.find("*") > line.rfind(":")): # unannotated pointer
@@ -65,21 +65,21 @@
sys.stderr.write("%s should be hand written\n" % (extern))
print "// FIXME: this function has pointers, it should be hand written"
externs.append(extern)
-
+
print "%s Debug_%s(%s)\n{" % (returnType, functionName, RemoveAnnotation(parameterList))
print " glesv2debugger::Message msg;"
-
+
if parameterList == "void":
parameters = []
arguments = ""
paramNames = []
inout = ""
getData = ""
-
+
callerMembers = ""
setCallerMembers = ""
setMsgParameters = ""
-
+
for parameter in parameters:
const = parameter.find("const")
parameter = parameter.replace("const", "")
@@ -107,7 +107,7 @@
annotation = "strlen(%s)" % (paramName)
else:
count = int(annotation)
-
+
setMsgParameters += " msg.set_arg%d(ToInt(%s));\n" % (paramIndex, paramName)
if paramType.find("void") >= 0:
getData += " msg.mutable_data()->assign(reinterpret_cast<const char *>(%s), %s * sizeof(char));" % (paramName, annotation)
@@ -127,7 +127,7 @@
paramIndex += 1
callerMembers += " %s %s;\n" % (paramType, paramName)
setCallerMembers += " caller.%s = %s;\n" % (paramName, paramName)
-
+
print " struct : public FunctionCall {"
print callerMembers
print " const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {"
@@ -141,6 +141,11 @@
if inout in ["out", "inout"]:
print " msg.set_time((systemTime(timeMode) - c0) * 1e-6f);"
print " " + getData
+ if functionName in extendFunctions:
+ print "\
+#ifdef EXTEND_AFTER_CALL_Debug_%s\n\
+ EXTEND_AFTER_CALL_Debug_%s;\n\
+#endif" % (functionName, functionName)
if functionName in contextFunctions:
print " getDbgContextThreadSpecific()->%s(%s);" % (functionName, arguments)
if returnType == "void":
@@ -157,7 +162,10 @@
if inout in ["in", "inout"]:
print getData
if functionName in extendFunctions:
- print " EXTEND_Debug_%s;" % (functionName)
+ print "\
+#ifdef EXTEND_Debug_%s\n\
+ EXTEND_Debug_%s;\n\
+#endif" % (functionName, functionName)
print " int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_%s);"\
% (functionName)
if returnType != "void":
@@ -166,8 +174,8 @@
else:
print " return reinterpret_cast<%s>(ret);" % (returnType)
print "}\n"
-
-
+
+
print "// FIXME: the following functions should be written by hand"
for extern in externs:
print extern
@@ -189,18 +197,23 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-
+
// auto generated by generate_api_cpp.py
+#include <utils/Debug.h>
+
#include "src/header.h"
#include "src/api.h"
-template<typename T> static int ToInt(const T & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (int &)t; }
-template<typename T> static T FromInt(const int & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (T &)t; }
-"""
+template<typename T> static int ToInt(const T & t)
+{
+ COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(T) == sizeof(int));
+ return (int &)t;
+}
+"""
lines = open("gl2_api_annotated.in").readlines()
generate_api(lines)
#lines = open("gl2ext_api.in").readlines()
#generate_api(lines)
-
+
diff --git a/opengl/libs/GLES2_dbg/generate_caller_cpp.py b/opengl/libs/GLES2_dbg/generate_caller_cpp.py
index eac2292..ee4208d 100755
--- a/opengl/libs/GLES2_dbg/generate_caller_cpp.py
+++ b/opengl/libs/GLES2_dbg/generate_caller_cpp.py
@@ -177,7 +177,6 @@
{
LOGD("GenerateCall function=%u", cmd.function());
const int * ret = prevRet; // only some functions have return value
- gl_hooks_t::gl_t const * const _c = &getGLTraceThreadSpecific()->gl;
nsecs_t c0 = systemTime(timeMode);
switch (cmd.function()) {""")
diff --git a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
index 466c447..57e008c 100755
--- a/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
+++ b/opengl/libs/GLES2_dbg/generate_debugger_message_proto.py
@@ -70,41 +70,43 @@
""")
i = 0;
-
+
lines = open("gl2_api_annotated.in").readlines()
i = generate_gl_entries(output, lines, i)
output.write(" // end of GL functions\n")
-
+
#lines = open("gl2ext_api.in").readlines()
#i = generate_gl_entries(output, lines, i)
#output.write(" // end of GL EXT functions\n")
-
+
lines = open("../EGL/egl_entries.in").readlines()
i = generate_egl_entries(output, lines, i)
output.write(" // end of GL EXT functions\n")
-
+
output.write(" ACK = %d;\n" % (i))
i += 1
-
+
output.write(" NEG = %d;\n" % (i))
i += 1
-
+
output.write(" CONTINUE = %d;\n" % (i))
i += 1
-
+
output.write(" SKIP = %d;\n" % (i))
i += 1
-
+
output.write(" SETPROP = %d;\n" % (i))
i += 1
-
+
output.write(""" }
required Function function = 2 [default = NEG]; // type/function of message
enum Type
{
BeforeCall = 0;
AfterCall = 1;
- Response = 2; // currently used for misc messages
+ AfterGeneratedCall = 2;
+ Response = 3; // currently used for misc messages
+ CompleteCall = 4; // BeforeCall and AfterCall merged
}
required Type type = 3;
required bool expect_response = 4;
@@ -125,16 +127,22 @@
ReferencedImage = 0; // for image sourced from ReadPixels
NonreferencedImage = 1; // for image sourced from ReadPixels
};
- optional DataType data_type = 23; // most data types can be inferred from function
- optional int32 pixel_format = 24; // used for image data if format and type
- optional int32 pixel_type = 25; // cannot be determined from arg
-
+ // most data types can be inferred from function
+ optional DataType data_type = 23;
+ // these are used for image data when they cannot be determined from args
+ optional int32 pixel_format = 24;
+ optional int32 pixel_type = 25;
+ optional int32 image_width = 26;
+ optional int32 image_height = 27;
+
optional float time = 11; // duration of previous GL call (ms)
enum Prop
{
- Capture = 0; // arg0 = true | false
+ CaptureDraw = 0; // arg0 = number of glDrawArrays/Elements to glReadPixels
TimeMode = 1; // arg0 = SYSTEM_TIME_* in utils/Timers.h
ExpectResponse = 2; // arg0 = enum Function, arg1 = true/false
+ CaptureSwap = 3; // arg0 = number of eglSwapBuffers to glReadPixels
+ GLConstant = 4; // arg0 = GLenum, arg1 = constant; send GL impl. constants
};
optional Prop prop = 21; // used with SETPROP, value in arg0
optional float clock = 22; // wall clock in seconds
@@ -142,6 +150,6 @@
""")
output.close()
-
+
os.system("aprotoc --cpp_out=src --java_out=../../../../../development/tools/glesv2debugger/src debugger_message.proto")
os.system('mv -f "src/debugger_message.pb.cc" "src/debugger_message.pb.cpp"')
diff --git a/opengl/libs/GLES2_dbg/src/api.cpp b/opengl/libs/GLES2_dbg/src/api.cpp
index 130ca7e..c483547 100644
--- a/opengl/libs/GLES2_dbg/src/api.cpp
+++ b/opengl/libs/GLES2_dbg/src/api.cpp
@@ -16,11 +16,16 @@
// auto generated by generate_api_cpp.py
+#include <utils/Debug.h>
+
#include "src/header.h"
#include "src/api.h"
-template<typename T> static int ToInt(const T & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (int &)t; }
-template<typename T> static T FromInt(const int & t) { STATIC_ASSERT(sizeof(T) == sizeof(int), bitcast); return (T &)t; }
+template<typename T> static int ToInt(const T & t)
+{
+ COMPILE_TIME_ASSERT_FUNCTION_SCOPE(sizeof(T) == sizeof(int));
+ return (int &)t;
+}
void Debug_glActiveTexture(GLenum texture)
{
@@ -592,6 +597,9 @@
const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
_c->glCopyTexImage2D(target, level, internalformat, x, y, width, height, border);
+#ifdef EXTEND_AFTER_CALL_Debug_glCopyTexImage2D
+ EXTEND_AFTER_CALL_Debug_glCopyTexImage2D;
+#endif
return 0;
}
} caller;
@@ -613,7 +621,9 @@
msg.set_arg6(height);
msg.set_arg7(border);
+#ifdef EXTEND_Debug_glCopyTexImage2D
EXTEND_Debug_glCopyTexImage2D;
+#endif
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glCopyTexImage2D);
}
@@ -632,6 +642,9 @@
const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
_c->glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
+#ifdef EXTEND_AFTER_CALL_Debug_glCopyTexSubImage2D
+ EXTEND_AFTER_CALL_Debug_glCopyTexSubImage2D;
+#endif
return 0;
}
} caller;
@@ -653,7 +666,9 @@
msg.set_arg6(width);
msg.set_arg7(height);
+#ifdef EXTEND_Debug_glCopyTexSubImage2D
EXTEND_Debug_glCopyTexSubImage2D;
+#endif
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glCopyTexSubImage2D);
}
@@ -2164,6 +2179,49 @@
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glPolygonOffset);
}
+void Debug_glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
+{
+ glesv2debugger::Message msg;
+ struct : public FunctionCall {
+ GLint x;
+ GLint y;
+ GLsizei width;
+ GLsizei height;
+ GLenum format;
+ GLenum type;
+ GLvoid* pixels;
+
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ _c->glReadPixels(x, y, width, height, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glReadPixels
+ EXTEND_AFTER_CALL_Debug_glReadPixels;
+#endif
+ return 0;
+ }
+ } caller;
+ caller.x = x;
+ caller.y = y;
+ caller.width = width;
+ caller.height = height;
+ caller.format = format;
+ caller.type = type;
+ caller.pixels = pixels;
+
+ msg.set_arg0(x);
+ msg.set_arg1(y);
+ msg.set_arg2(width);
+ msg.set_arg3(height);
+ msg.set_arg4(format);
+ msg.set_arg5(type);
+ msg.set_arg6(ToInt(pixels));
+
+ // FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glReadPixels
+ EXTEND_Debug_glReadPixels;
+#endif
+ int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glReadPixels);
+}
+
void Debug_glReleaseShaderCompiler(void)
{
glesv2debugger::Message msg;
@@ -2297,6 +2355,9 @@
const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
_c->glShaderSource(shader, count, string, length);
+#ifdef EXTEND_AFTER_CALL_Debug_glShaderSource
+ EXTEND_AFTER_CALL_Debug_glShaderSource;
+#endif
return 0;
}
} caller;
@@ -2311,7 +2372,9 @@
msg.set_arg3(ToInt(length));
// FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glShaderSource
EXTEND_Debug_glShaderSource;
+#endif
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glShaderSource);
}
@@ -2472,6 +2535,9 @@
const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
_c->glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glTexImage2D
+ EXTEND_AFTER_CALL_Debug_glTexImage2D;
+#endif
return 0;
}
} caller;
@@ -2496,7 +2562,9 @@
msg.set_arg8(ToInt(pixels));
// FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glTexImage2D
EXTEND_Debug_glTexImage2D;
+#endif
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glTexImage2D);
}
@@ -2616,6 +2684,9 @@
const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
_c->glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
+#ifdef EXTEND_AFTER_CALL_Debug_glTexSubImage2D
+ EXTEND_AFTER_CALL_Debug_glTexSubImage2D;
+#endif
return 0;
}
} caller;
@@ -2640,7 +2711,9 @@
msg.set_arg8(ToInt(pixels));
// FIXME: check for pointer usage
+#ifdef EXTEND_Debug_glTexSubImage2D
EXTEND_Debug_glTexSubImage2D;
+#endif
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_glTexSubImage2D);
}
diff --git a/opengl/libs/GLES2_dbg/src/api.h b/opengl/libs/GLES2_dbg/src/api.h
index b9fc341..0b227bc 100644
--- a/opengl/libs/GLES2_dbg/src/api.h
+++ b/opengl/libs/GLES2_dbg/src/api.h
@@ -16,19 +16,29 @@
#define EXTEND_Debug_glCopyTexImage2D \
DbgContext * const dbg = getDbgContextThreadSpecific(); \
- GLint readFormat, readType; \
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat); \
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType); \
- unsigned readSize = GetBytesPerPixel(readFormat, readType) * width * height; \
- void * readData = dbg->GetReadPixelsBuffer(readSize); \
- dbg->hooks->gl.glReadPixels(x, y, width, height, readFormat, readType, readData); \
+ void * readData = dbg->GetReadPixelsBuffer(4 * width * height); \
+ /* pick easy format for client to convert */ \
+ dbg->hooks->gl.glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, readData); \
dbg->CompressReadPixelBuffer(msg.mutable_data()); \
msg.set_data_type(msg.ReferencedImage); \
- msg.set_pixel_format(readFormat); \
- msg.set_pixel_type(readType);
+ msg.set_pixel_format(GL_RGBA); \
+ msg.set_pixel_type(GL_UNSIGNED_BYTE);
#define EXTEND_Debug_glCopyTexSubImage2D EXTEND_Debug_glCopyTexImage2D
+#define EXTEND_AFTER_CALL_Debug_glReadPixels \
+ { \
+ DbgContext * const dbg = getDbgContextThreadSpecific(); \
+ if (dbg->IsReadPixelBuffer(pixels)) { \
+ dbg->CompressReadPixelBuffer(msg.mutable_data()); \
+ msg.set_data_type(msg.ReferencedImage); \
+ } else { \
+ const unsigned int size = width * height * GetBytesPerPixel(format, type); \
+ dbg->Compress(pixels, size, msg.mutable_data()); \
+ msg.set_data_type(msg.NonreferencedImage); \
+ } \
+ }
+
#define EXTEND_Debug_glShaderSource \
std::string * const data = msg.mutable_data(); \
for (unsigned i = 0; i < count; i++) \
diff --git a/opengl/libs/GLES2_dbg/src/caller.cpp b/opengl/libs/GLES2_dbg/src/caller.cpp
index 9992f05..6b72751 100644
--- a/opengl/libs/GLES2_dbg/src/caller.cpp
+++ b/opengl/libs/GLES2_dbg/src/caller.cpp
@@ -105,7 +105,6 @@
{
LOGD("GenerateCall function=%u", cmd.function());
const int * ret = prevRet; // only some functions have return value
- gl_hooks_t::gl_t const * const _c = &getGLTraceThreadSpecific()->gl;
nsecs_t c0 = systemTime(timeMode);
switch (cmd.function()) { case glesv2debugger::Message_Function_glActiveTexture:
dbg->hooks->gl.glActiveTexture(
@@ -772,7 +771,7 @@
msg.set_time((systemTime(timeMode) - c0) * 1e-6f);
msg.set_context_id(reinterpret_cast<int>(dbg));
msg.set_function(cmd.function());
- msg.set_type(glesv2debugger::Message_Type_AfterCall);
+ msg.set_type(glesv2debugger::Message_Type_AfterGeneratedCall);
return ret;
}
diff --git a/opengl/libs/GLES2_dbg/src/caller.h b/opengl/libs/GLES2_dbg/src/caller.h
index 5447757..e8111b3 100644
--- a/opengl/libs/GLES2_dbg/src/caller.h
+++ b/opengl/libs/GLES2_dbg/src/caller.h
@@ -138,7 +138,9 @@
const glesv2debugger::Message & cmd,
glesv2debugger::Message & msg, const int * const prevRet)
{
- assert(0);
+ GLint params = -1;
+ dbg->hooks->gl.glGetProgramiv(cmd.arg0(), cmd.arg1(), ¶ms);
+ msg.mutable_data()->append(reinterpret_cast<char *>(¶ms), sizeof(params));
return prevRet;
}
@@ -146,7 +148,10 @@
const glesv2debugger::Message & cmd,
glesv2debugger::Message & msg, const int * const prevRet)
{
- assert(0);
+ const GLsizei bufSize = static_cast<GLsizei>(dbg->GetBufferSize());
+ GLsizei length = -1;
+ dbg->hooks->gl.glGetProgramInfoLog(cmd.arg0(), bufSize, &length, dbg->GetBuffer());
+ msg.mutable_data()->append(dbg->GetBuffer(), length);
return prevRet;
}
@@ -162,7 +167,9 @@
const glesv2debugger::Message & cmd,
glesv2debugger::Message & msg, const int * const prevRet)
{
- assert(0);
+ GLint params = -1;
+ dbg->hooks->gl.glGetShaderiv(cmd.arg0(), cmd.arg1(), ¶ms);
+ msg.mutable_data()->append(reinterpret_cast<char *>(¶ms), sizeof(params));
return prevRet;
}
@@ -170,7 +177,10 @@
const glesv2debugger::Message & cmd,
glesv2debugger::Message & msg, const int * const prevRet)
{
- assert(0);
+ const GLsizei bufSize = static_cast<GLsizei>(dbg->GetBufferSize());
+ GLsizei length = -1;
+ dbg->hooks->gl.glGetShaderInfoLog(cmd.arg0(), bufSize, &length, dbg->GetBuffer());
+ msg.mutable_data()->append(dbg->GetBuffer(), length);
return prevRet;
}
diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
index cc7336c..7f5b27b 100644
--- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
+++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp
@@ -15,6 +15,7 @@
*/
#include "header.h"
+#include "egl_tls.h"
extern "C"
{
@@ -24,11 +25,23 @@
namespace android
{
+pthread_key_t dbgEGLThreadLocalStorageKey = -1;
+
+DbgContext * getDbgContextThreadSpecific()
+{
+ tls_t* tls = (tls_t*)pthread_getspecific(dbgEGLThreadLocalStorageKey);
+ return tls->dbg;
+}
+
DbgContext::DbgContext(const unsigned version, const gl_hooks_t * const hooks,
- const unsigned MAX_VERTEX_ATTRIBS)
+ const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
+ const GLenum readType)
: lzf_buf(NULL), lzf_readIndex(0), lzf_refSize(0), lzf_refBufSize(0)
, version(version), hooks(hooks)
, MAX_VERTEX_ATTRIBS(MAX_VERTEX_ATTRIBS)
+ , readFormat(readFormat), readType(readType)
+ , readBytesPerPixel(GetBytesPerPixel(readFormat, readType))
+ , captureSwap(0), captureDraw(0)
, vertexAttribs(new VertexAttrib[MAX_VERTEX_ATTRIBS])
, hasNonVBOAttribs(false), indexBuffers(NULL), indexBuffer(NULL)
, program(0), maxAttrib(0)
@@ -47,13 +60,35 @@
free(lzf_ref[1]);
}
-DbgContext * CreateDbgContext(const unsigned version, const gl_hooks_t * const hooks)
+DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
+ const unsigned version, const gl_hooks_t * const hooks)
{
+ dbgEGLThreadLocalStorageKey = EGLThreadLocalStorageKey;
assert(version < 2);
assert(GL_NO_ERROR == hooks->gl.glGetError());
GLint MAX_VERTEX_ATTRIBS = 0;
hooks->gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &MAX_VERTEX_ATTRIBS);
- return new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS);
+ GLint readFormat, readType;
+ hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
+ hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
+ DbgContext * const dbg = new DbgContext(version, hooks, MAX_VERTEX_ATTRIBS, readFormat, readType);
+
+ glesv2debugger::Message msg, cmd;
+ msg.set_context_id(reinterpret_cast<int>(dbg));
+ msg.set_expect_response(false);
+ msg.set_type(msg.Response);
+ msg.set_function(msg.SETPROP);
+ msg.set_prop(msg.GLConstant);
+ msg.set_arg0(GL_MAX_VERTEX_ATTRIBS);
+ msg.set_arg1(MAX_VERTEX_ATTRIBS);
+ Send(msg, cmd);
+
+ GLint MAX_COMBINED_TEXTURE_IMAGE_UNITS = 0;
+ hooks->gl.glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+ msg.set_arg0(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+ msg.set_arg1(MAX_COMBINED_TEXTURE_IMAGE_UNITS);
+ Send(msg, cmd);
+ return dbg;
}
void DestroyDbgContext(DbgContext * const dbg)
@@ -113,6 +148,7 @@
{
if (!lzf_buf)
lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+ assert(lzf_buf);
const uint32_t totalDecompSize = in_len;
outStr->append((const char *)&totalDecompSize, sizeof(totalDecompSize));
for (unsigned int i = 0; i < in_len; i += LZF_CHUNK_SIZE) {
@@ -130,13 +166,46 @@
}
}
+unsigned char * DbgContext::Decompress(const void * in, const unsigned int inLen,
+ unsigned int * const outLen)
+{
+ assert(inLen > 4 * 3);
+ if (inLen < 4 * 3)
+ return NULL;
+ *outLen = *(uint32_t *)in;
+ unsigned char * const out = (unsigned char *)malloc(*outLen);
+ unsigned int outPos = 0;
+ const unsigned char * const end = (const unsigned char *)in + inLen;
+ for (const unsigned char * inData = (const unsigned char *)in + 4; inData < end; ) {
+ const uint32_t chunkOut = *(uint32_t *)inData;
+ inData += 4;
+ const uint32_t chunkIn = *(uint32_t *)inData;
+ inData += 4;
+ if (chunkIn > 0) {
+ assert(inData + chunkIn <= end);
+ assert(outPos + chunkOut <= *outLen);
+ outPos += lzf_decompress(inData, chunkIn, out + outPos, chunkOut);
+ inData += chunkIn;
+ } else {
+ assert(inData + chunkOut <= end);
+ assert(outPos + chunkOut <= *outLen);
+ memcpy(out + outPos, inData, chunkOut);
+ inData += chunkOut;
+ outPos += chunkOut;
+ }
+ }
+ return out;
+}
+
void * DbgContext::GetReadPixelsBuffer(const unsigned size)
{
if (lzf_refBufSize < size + 8) {
lzf_refBufSize = size + 8;
lzf_ref[0] = (unsigned *)realloc(lzf_ref[0], lzf_refBufSize);
+ assert(lzf_ref[0]);
memset(lzf_ref[0], 0, lzf_refBufSize);
lzf_ref[1] = (unsigned *)realloc(lzf_ref[1], lzf_refBufSize);
+ assert(lzf_ref[1]);
memset(lzf_ref[1], 0, lzf_refBufSize);
}
if (lzf_refSize != size) // need to clear unused ref to maintain consistency
@@ -151,6 +220,7 @@
void DbgContext::CompressReadPixelBuffer(std::string * const outStr)
{
+ assert(lzf_ref[0] && lzf_ref[1]);
unsigned * const ref = lzf_ref[lzf_readIndex ^ 1];
unsigned * const src = lzf_ref[lzf_readIndex];
for (unsigned i = 0; i < lzf_refSize / sizeof(*ref) + 1; i++)
@@ -158,13 +228,34 @@
Compress(ref, lzf_refSize, outStr);
}
+char * DbgContext::GetBuffer()
+{
+ if (!lzf_buf)
+ lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+ assert(lzf_buf);
+ return lzf_buf;
+}
+
+unsigned int DbgContext::GetBufferSize()
+{
+ if (!lzf_buf)
+ lzf_buf = (char *)malloc(LZF_CHUNK_SIZE);
+ assert(lzf_buf);
+ if (lzf_buf)
+ return LZF_CHUNK_SIZE;
+ else
+ return 0;
+}
+
void DbgContext::glUseProgram(GLuint program)
{
while (GLenum error = hooks->gl.glGetError())
- LOGD("DbgContext::glUseProgram: before glGetError() = 0x%.4X", error);
-
+ LOGD("DbgContext::glUseProgram(%u): before glGetError() = 0x%.4X",
+ program, error);
this->program = program;
-
+ maxAttrib = 0;
+ if (program == 0)
+ return;
GLint activeAttributes = 0;
hooks->gl.glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &activeAttributes);
maxAttrib = 0;
@@ -202,9 +293,9 @@
maxAttrib = slot;
}
delete name;
-
while (GLenum error = hooks->gl.glGetError())
- LOGD("DbgContext::glUseProgram: after glGetError() = 0x%.4X", error);
+ LOGD("DbgContext::glUseProgram(%u): after glGetError() = 0x%.4X",
+ program, error);
}
static bool HasNonVBOAttribs(const DbgContext * const ctx)
@@ -254,14 +345,16 @@
void DbgContext::glEnableVertexAttribArray(GLuint index)
{
- assert(index < MAX_VERTEX_ATTRIBS);
+ if (index >= MAX_VERTEX_ATTRIBS)
+ return;
vertexAttribs[index].enabled = true;
hasNonVBOAttribs = HasNonVBOAttribs(this);
}
void DbgContext::glDisableVertexAttribArray(GLuint index)
{
- assert(index < MAX_VERTEX_ATTRIBS);
+ if (index >= MAX_VERTEX_ATTRIBS)
+ return;
vertexAttribs[index].enabled = false;
hasNonVBOAttribs = HasNonVBOAttribs(this);
}
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
index 046c954..50f70f7 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.cpp
@@ -436,6 +436,8 @@
case 0:
case 1:
case 2:
+ case 3:
+ case 4:
return true;
default:
return false;
@@ -445,7 +447,9 @@
#ifndef _MSC_VER
const Message_Type Message::BeforeCall;
const Message_Type Message::AfterCall;
+const Message_Type Message::AfterGeneratedCall;
const Message_Type Message::Response;
+const Message_Type Message::CompleteCall;
const Message_Type Message::Type_MIN;
const Message_Type Message::Type_MAX;
const int Message::Type_ARRAYSIZE;
@@ -472,6 +476,8 @@
case 0:
case 1:
case 2:
+ case 3:
+ case 4:
return true;
default:
return false;
@@ -479,9 +485,11 @@
}
#ifndef _MSC_VER
-const Message_Prop Message::Capture;
+const Message_Prop Message::CaptureDraw;
const Message_Prop Message::TimeMode;
const Message_Prop Message::ExpectResponse;
+const Message_Prop Message::CaptureSwap;
+const Message_Prop Message::GLConstant;
const Message_Prop Message::Prop_MIN;
const Message_Prop Message::Prop_MAX;
const int Message::Prop_ARRAYSIZE;
@@ -506,6 +514,8 @@
const int Message::kDataTypeFieldNumber;
const int Message::kPixelFormatFieldNumber;
const int Message::kPixelTypeFieldNumber;
+const int Message::kImageWidthFieldNumber;
+const int Message::kImageHeightFieldNumber;
const int Message::kTimeFieldNumber;
const int Message::kPropFieldNumber;
const int Message::kClockFieldNumber;
@@ -545,6 +555,8 @@
data_type_ = 0;
pixel_format_ = 0;
pixel_type_ = 0;
+ image_width_ = 0;
+ image_height_ = 0;
time_ = 0;
prop_ = 0;
clock_ = 0;
@@ -606,6 +618,8 @@
if (_has_bits_[16 / 32] & (0xffu << (16 % 32))) {
pixel_format_ = 0;
pixel_type_ = 0;
+ image_width_ = 0;
+ image_height_ = 0;
time_ = 0;
prop_ = 0;
clock_ = 0;
@@ -790,7 +804,7 @@
DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
input, &time_)));
- _set_bit(18);
+ _set_bit(20);
} else {
goto handle_uninterpreted;
}
@@ -905,7 +919,7 @@
DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
float, ::google::protobuf::internal::WireFormatLite::TYPE_FLOAT>(
input, &clock_)));
- _set_bit(20);
+ _set_bit(22);
} else {
goto handle_uninterpreted;
}
@@ -960,6 +974,38 @@
} else {
goto handle_uninterpreted;
}
+ if (input->ExpectTag(208)) goto parse_image_width;
+ break;
+ }
+
+ // optional int32 image_width = 26;
+ case 26: {
+ if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+ parse_image_width:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::int32, ::google::protobuf::internal::WireFormatLite::TYPE_INT32>(
+ input, &image_width_)));
+ _set_bit(18);
+ } else {
+ goto handle_uninterpreted;
+ }
+ if (input->ExpectTag(216)) goto parse_image_height;
+ break;
+ }
+
+ // optional int32 image_height = 27;
+ case 27: {
+ if (::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_VARINT) {
+ parse_image_height:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::int32, ::google::protobuf::internal::WireFormatLite::TYPE_INT32>(
+ input, &image_height_)));
+ _set_bit(19);
+ } else {
+ goto handle_uninterpreted;
+ }
if (input->ExpectAtEnd()) return true;
break;
}
@@ -1035,7 +1081,7 @@
}
// optional float time = 11;
- if (_has_bit(18)) {
+ if (_has_bit(20)) {
::google::protobuf::internal::WireFormatLite::WriteFloat(11, this->time(), output);
}
@@ -1065,13 +1111,13 @@
}
// optional .com.android.glesv2debugger.Message.Prop prop = 21;
- if (_has_bit(19)) {
+ if (_has_bit(21)) {
::google::protobuf::internal::WireFormatLite::WriteEnum(
21, this->prop(), output);
}
// optional float clock = 22;
- if (_has_bit(20)) {
+ if (_has_bit(22)) {
::google::protobuf::internal::WireFormatLite::WriteFloat(22, this->clock(), output);
}
@@ -1091,6 +1137,16 @@
::google::protobuf::internal::WireFormatLite::WriteInt32(25, this->pixel_type(), output);
}
+ // optional int32 image_width = 26;
+ if (_has_bit(18)) {
+ ::google::protobuf::internal::WireFormatLite::WriteInt32(26, this->image_width(), output);
+ }
+
+ // optional int32 image_height = 27;
+ if (_has_bit(19)) {
+ ::google::protobuf::internal::WireFormatLite::WriteInt32(27, this->image_height(), output);
+ }
+
}
int Message::ByteSize() const {
@@ -1222,6 +1278,20 @@
this->pixel_type());
}
+ // optional int32 image_width = 26;
+ if (has_image_width()) {
+ total_size += 2 +
+ ::google::protobuf::internal::WireFormatLite::Int32Size(
+ this->image_width());
+ }
+
+ // optional int32 image_height = 27;
+ if (has_image_height()) {
+ total_size += 2 +
+ ::google::protobuf::internal::WireFormatLite::Int32Size(
+ this->image_height());
+ }
+
// optional float time = 11;
if (has_time()) {
total_size += 1 + 4;
@@ -1312,12 +1382,18 @@
set_pixel_type(from.pixel_type());
}
if (from._has_bit(18)) {
- set_time(from.time());
+ set_image_width(from.image_width());
}
if (from._has_bit(19)) {
- set_prop(from.prop());
+ set_image_height(from.image_height());
}
if (from._has_bit(20)) {
+ set_time(from.time());
+ }
+ if (from._has_bit(21)) {
+ set_prop(from.prop());
+ }
+ if (from._has_bit(22)) {
set_clock(from.clock());
}
}
@@ -1355,6 +1431,8 @@
std::swap(data_type_, other->data_type_);
std::swap(pixel_format_, other->pixel_format_);
std::swap(pixel_type_, other->pixel_type_);
+ std::swap(image_width_, other->image_width_);
+ std::swap(image_height_, other->image_height_);
std::swap(time_, other->time_);
std::swap(prop_, other->prop_);
std::swap(clock_, other->clock_);
diff --git a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
index b2ec5a0..5c94664 100644
--- a/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
+++ b/opengl/libs/GLES2_dbg/src/debugger_message.pb.h
@@ -236,11 +236,13 @@
enum Message_Type {
Message_Type_BeforeCall = 0,
Message_Type_AfterCall = 1,
- Message_Type_Response = 2
+ Message_Type_AfterGeneratedCall = 2,
+ Message_Type_Response = 3,
+ Message_Type_CompleteCall = 4
};
bool Message_Type_IsValid(int value);
const Message_Type Message_Type_Type_MIN = Message_Type_BeforeCall;
-const Message_Type Message_Type_Type_MAX = Message_Type_Response;
+const Message_Type Message_Type_Type_MAX = Message_Type_CompleteCall;
const int Message_Type_Type_ARRAYSIZE = Message_Type_Type_MAX + 1;
enum Message_DataType {
@@ -253,13 +255,15 @@
const int Message_DataType_DataType_ARRAYSIZE = Message_DataType_DataType_MAX + 1;
enum Message_Prop {
- Message_Prop_Capture = 0,
+ Message_Prop_CaptureDraw = 0,
Message_Prop_TimeMode = 1,
- Message_Prop_ExpectResponse = 2
+ Message_Prop_ExpectResponse = 2,
+ Message_Prop_CaptureSwap = 3,
+ Message_Prop_GLConstant = 4
};
bool Message_Prop_IsValid(int value);
-const Message_Prop Message_Prop_Prop_MIN = Message_Prop_Capture;
-const Message_Prop Message_Prop_Prop_MAX = Message_Prop_ExpectResponse;
+const Message_Prop Message_Prop_Prop_MIN = Message_Prop_CaptureDraw;
+const Message_Prop Message_Prop_Prop_MAX = Message_Prop_GLConstant;
const int Message_Prop_Prop_ARRAYSIZE = Message_Prop_Prop_MAX + 1;
// ===================================================================
@@ -510,7 +514,9 @@
typedef Message_Type Type;
static const Type BeforeCall = Message_Type_BeforeCall;
static const Type AfterCall = Message_Type_AfterCall;
+ static const Type AfterGeneratedCall = Message_Type_AfterGeneratedCall;
static const Type Response = Message_Type_Response;
+ static const Type CompleteCall = Message_Type_CompleteCall;
static inline bool Type_IsValid(int value) {
return Message_Type_IsValid(value);
}
@@ -535,9 +541,11 @@
Message_DataType_DataType_ARRAYSIZE;
typedef Message_Prop Prop;
- static const Prop Capture = Message_Prop_Capture;
+ static const Prop CaptureDraw = Message_Prop_CaptureDraw;
static const Prop TimeMode = Message_Prop_TimeMode;
static const Prop ExpectResponse = Message_Prop_ExpectResponse;
+ static const Prop CaptureSwap = Message_Prop_CaptureSwap;
+ static const Prop GLConstant = Message_Prop_GLConstant;
static inline bool Prop_IsValid(int value) {
return Message_Prop_IsValid(value);
}
@@ -679,6 +687,20 @@
inline ::google::protobuf::int32 pixel_type() const;
inline void set_pixel_type(::google::protobuf::int32 value);
+ // optional int32 image_width = 26;
+ inline bool has_image_width() const;
+ inline void clear_image_width();
+ static const int kImageWidthFieldNumber = 26;
+ inline ::google::protobuf::int32 image_width() const;
+ inline void set_image_width(::google::protobuf::int32 value);
+
+ // optional int32 image_height = 27;
+ inline bool has_image_height() const;
+ inline void clear_image_height();
+ static const int kImageHeightFieldNumber = 27;
+ inline ::google::protobuf::int32 image_height() const;
+ inline void set_image_height(::google::protobuf::int32 value);
+
// optional float time = 11;
inline bool has_time() const;
inline void clear_time();
@@ -723,6 +745,8 @@
int data_type_;
::google::protobuf::int32 pixel_format_;
::google::protobuf::int32 pixel_type_;
+ ::google::protobuf::int32 image_width_;
+ ::google::protobuf::int32 image_height_;
float time_;
int prop_;
float clock_;
@@ -730,7 +754,7 @@
friend void protobuf_AssignDesc_debugger_5fmessage_2eproto();
friend void protobuf_ShutdownFile_debugger_5fmessage_2eproto();
- ::google::protobuf::uint32 _has_bits_[(21 + 31) / 32];
+ ::google::protobuf::uint32 _has_bits_[(23 + 31) / 32];
// WHY DOES & HAVE LOWER PRECEDENCE THAN != !?
inline bool _has_bit(int index) const {
@@ -1070,52 +1094,84 @@
pixel_type_ = value;
}
+// optional int32 image_width = 26;
+inline bool Message::has_image_width() const {
+ return _has_bit(18);
+}
+inline void Message::clear_image_width() {
+ image_width_ = 0;
+ _clear_bit(18);
+}
+inline ::google::protobuf::int32 Message::image_width() const {
+ return image_width_;
+}
+inline void Message::set_image_width(::google::protobuf::int32 value) {
+ _set_bit(18);
+ image_width_ = value;
+}
+
+// optional int32 image_height = 27;
+inline bool Message::has_image_height() const {
+ return _has_bit(19);
+}
+inline void Message::clear_image_height() {
+ image_height_ = 0;
+ _clear_bit(19);
+}
+inline ::google::protobuf::int32 Message::image_height() const {
+ return image_height_;
+}
+inline void Message::set_image_height(::google::protobuf::int32 value) {
+ _set_bit(19);
+ image_height_ = value;
+}
+
// optional float time = 11;
inline bool Message::has_time() const {
- return _has_bit(18);
+ return _has_bit(20);
}
inline void Message::clear_time() {
time_ = 0;
- _clear_bit(18);
+ _clear_bit(20);
}
inline float Message::time() const {
return time_;
}
inline void Message::set_time(float value) {
- _set_bit(18);
+ _set_bit(20);
time_ = value;
}
// optional .com.android.glesv2debugger.Message.Prop prop = 21;
inline bool Message::has_prop() const {
- return _has_bit(19);
+ return _has_bit(21);
}
inline void Message::clear_prop() {
prop_ = 0;
- _clear_bit(19);
+ _clear_bit(21);
}
inline ::com::android::glesv2debugger::Message_Prop Message::prop() const {
return static_cast< ::com::android::glesv2debugger::Message_Prop >(prop_);
}
inline void Message::set_prop(::com::android::glesv2debugger::Message_Prop value) {
GOOGLE_DCHECK(::com::android::glesv2debugger::Message_Prop_IsValid(value));
- _set_bit(19);
+ _set_bit(21);
prop_ = value;
}
// optional float clock = 22;
inline bool Message::has_clock() const {
- return _has_bit(20);
+ return _has_bit(22);
}
inline void Message::clear_clock() {
clock_ = 0;
- _clear_bit(20);
+ _clear_bit(22);
}
inline float Message::clock() const {
return clock_;
}
inline void Message::set_clock(float value) {
- _set_bit(20);
+ _set_bit(22);
clock_ = value;
}
diff --git a/opengl/libs/GLES2_dbg/src/egl.cpp b/opengl/libs/GLES2_dbg/src/egl.cpp
index 3a20e21..eb28d06 100644
--- a/opengl/libs/GLES2_dbg/src/egl.cpp
+++ b/opengl/libs/GLES2_dbg/src/egl.cpp
@@ -18,6 +18,7 @@
EGLBoolean Debug_eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)
{
+ DbgContext * const dbg = getDbgContextThreadSpecific();
glesv2debugger::Message msg;
struct : public FunctionCall {
EGLDisplay dpy;
@@ -33,7 +34,21 @@
msg.set_arg0(reinterpret_cast<int>(dpy));
msg.set_arg1(reinterpret_cast<int>(draw));
-
+ if (dbg->captureSwap > 0) {
+ dbg->captureSwap--;
+ int viewport[4] = {};
+ dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
+ void * pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
+ dbg->readBytesPerPixel);
+ dbg->hooks->gl.glReadPixels(viewport[0], viewport[1], viewport[2],
+ viewport[3], dbg->readFormat, dbg->readType, pixels);
+ dbg->CompressReadPixelBuffer(msg.mutable_data());
+ msg.set_data_type(msg.ReferencedImage);
+ msg.set_pixel_format(dbg->readFormat);
+ msg.set_pixel_type(dbg->readType);
+ msg.set_image_width(viewport[2]);
+ msg.set_image_height(viewport[3]);
+ }
int * ret = MessageLoop(caller, msg, glesv2debugger::Message_Function_eglSwapBuffers);
return static_cast<EGLBoolean>(reinterpret_cast<int>(ret));
}
diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h
index 9218da5..f2b1fa6 100644
--- a/opengl/libs/GLES2_dbg/src/header.h
+++ b/opengl/libs/GLES2_dbg/src/header.h
@@ -14,6 +14,9 @@
** limitations under the License.
*/
+#ifndef ANDROID_GLES2_DBG_HEADER_H
+#define ANDROID_GLES2_DBG_HEADER_H
+
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
@@ -24,9 +27,7 @@
#include <cutils/log.h>
#include <utils/Timers.h>
-#include <../../../libcore/include/StaticAssert.h>
-#define EGL_TRACE 1
#include "hooks.h"
#include "glesv2dbg.h"
@@ -39,8 +40,6 @@
using namespace android;
using namespace com::android;
-#define API_ENTRY(_api) Debug_##_api
-
#ifndef __location__
#define __HIERALLOC_STRING_0__(s) #s
#define __HIERALLOC_STRING_1__(s) __HIERALLOC_STRING_0__(s)
@@ -74,9 +73,10 @@
};
struct DbgContext {
-private:
static const unsigned int LZF_CHUNK_SIZE = 256 * 1024;
- char * lzf_buf; // malloc / free; for lzf chunk compression
+
+private:
+ char * lzf_buf; // malloc / free; for lzf chunk compression and other uses
// used as buffer and reference frame for ReadPixels; malloc/free
unsigned * lzf_ref [2];
@@ -84,9 +84,14 @@
unsigned lzf_refSize, lzf_refBufSize; // bytes
public:
- const unsigned version; // 0 is GLES1, 1 is GLES2
+ const unsigned int version; // 0 is GLES1, 1 is GLES2
const gl_hooks_t * const hooks;
- const unsigned MAX_VERTEX_ATTRIBS;
+ const unsigned int MAX_VERTEX_ATTRIBS;
+ const GLenum readFormat, readType; // implementation supported glReadPixels
+ const unsigned int readBytesPerPixel;
+
+ unsigned int captureSwap; // number of eglSwapBuffers to glReadPixels
+ unsigned int captureDraw; // number of glDrawArrays/Elements to glReadPixels
GLFunctionBitfield expectResponse;
@@ -119,16 +124,21 @@
unsigned maxAttrib; // number of slots used by program
DbgContext(const unsigned version, const gl_hooks_t * const hooks,
- const unsigned MAX_VERTEX_ATTRIBS);
+ const unsigned MAX_VERTEX_ATTRIBS, const GLenum readFormat,
+ const GLenum readType);
~DbgContext();
void Fetch(const unsigned index, std::string * const data) const;
void Compress(const void * in_data, unsigned in_len, std::string * const outStr);
+ static unsigned char * Decompress(const void * in, const unsigned int inLen,
+ unsigned int * const outLen); // malloc/free
void * GetReadPixelsBuffer(const unsigned size);
bool IsReadPixelBuffer(const void * const ptr) {
return ptr == lzf_ref[lzf_readIndex];
}
void CompressReadPixelBuffer(std::string * const outStr);
+ char * GetBuffer(); // allocates lzf_buf if NULL
+ unsigned int GetBufferSize(); // allocates lzf_buf if NULL
void glUseProgram(GLuint program);
void glEnableVertexAttribArray(GLuint index);
@@ -141,9 +151,7 @@
void glDeleteBuffers(GLsizei n, const GLuint *buffers);
};
-
DbgContext * getDbgContextThreadSpecific();
-#define DBGCONTEXT(ctx) DbgContext * const ctx = getDbgContextThreadSpecific();
struct FunctionCall {
virtual const int * operator()(gl_hooks_t::gl_t const * const _c,
@@ -152,7 +160,6 @@
};
// move these into DbgContext as static
-extern bool capture;
extern int timeMode; // SYSTEM_TIME_
extern int clientSock, serverSock;
@@ -169,3 +176,5 @@
const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & cmd,
glesv2debugger::Message & msg, const int * const prevRet);
}; // namespace android {
+
+#endif // #ifndef ANDROID_GLES2_DBG_HEADER_H
diff --git a/opengl/libs/GLES2_dbg/src/server.cpp b/opengl/libs/GLES2_dbg/src/server.cpp
index 7039c84..0c711bf 100644
--- a/opengl/libs/GLES2_dbg/src/server.cpp
+++ b/opengl/libs/GLES2_dbg/src/server.cpp
@@ -28,7 +28,8 @@
{
int serverSock = -1, clientSock = -1;
-
+FILE * file = NULL;
+unsigned int MAX_FILE_SIZE = 0;
int timeMode = SYSTEM_TIME_THREAD;
static void Die(const char * msg)
@@ -38,18 +39,25 @@
exit(1);
}
-void StartDebugServer(unsigned short port)
+void StartDebugServer(const unsigned short port, const bool forceUseFile,
+ const unsigned int maxFileSize, const char * const filePath)
{
+ MAX_FILE_SIZE = maxFileSize;
+
LOGD("GLESv2_dbg: StartDebugServer");
- if (serverSock >= 0)
+ if (serverSock >= 0 || file)
return;
LOGD("GLESv2_dbg: StartDebugServer create socket");
struct sockaddr_in server = {}, client = {};
/* Create the TCP socket */
- if ((serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
- Die("Failed to create socket");
+ if (forceUseFile || (serverSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
+ file = fopen(filePath, "wb");
+ if (!file)
+ Die("Failed to create socket and file");
+ else
+ return;
}
/* Construct the server sockaddr_in structure */
server.sin_family = AF_INET; /* Internet/IP */
@@ -92,13 +100,17 @@
close(serverSock);
serverSock = -1;
}
-
+ if (file) {
+ fclose(file);
+ file = NULL;
+ }
}
void Receive(glesv2debugger::Message & cmd)
{
+ if (clientSock < 0)
+ return;
unsigned len = 0;
-
int received = recv(clientSock, &len, 4, MSG_WAITALL);
if (received < 0)
Die("Failed to receive response length");
@@ -106,7 +118,6 @@
LOGD("received %dB: %.8X", received, len);
Die("Received length mismatch, expected 4");
}
- len = ntohl(len);
static void * buffer = NULL;
static unsigned bufferSize = 0;
if (bufferSize < len) {
@@ -125,6 +136,8 @@
bool TryReceive(glesv2debugger::Message & cmd)
{
+ if (clientSock < 0)
+ return false;
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(clientSock, &readSet);
@@ -146,14 +159,34 @@
float Send(const glesv2debugger::Message & msg, glesv2debugger::Message & cmd)
{
+ // TODO: use per DbgContext send/receive buffer and async socket
+ // instead of mutex and blocking io; watch out for large messages
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- pthread_mutex_lock(&mutex); // TODO: this is just temporary
+ struct Autolock {
+ Autolock() {
+ pthread_mutex_lock(&mutex);
+ }
+ ~Autolock() {
+ pthread_mutex_unlock(&mutex);
+ }
+ } autolock;
if (msg.function() != glesv2debugger::Message_Function_ACK)
assert(msg.has_context_id() && msg.context_id() != 0);
static std::string str;
msg.SerializeToString(&str);
- uint32_t len = htonl(str.length());
+ const uint32_t len = str.length();
+ if (clientSock < 0) {
+ if (file) {
+ fwrite(&len, sizeof(len), 1, file);
+ fwrite(str.data(), len, 1, file);
+ if (ftell(file) >= MAX_FILE_SIZE) {
+ fclose(file);
+ Die("MAX_FILE_SIZE reached");
+ }
+ }
+ return 0;
+ }
int sent = -1;
sent = send(clientSock, &len, sizeof(len), 0);
if (sent != sizeof(len)) {
@@ -161,36 +194,37 @@
Die("Failed to send message length");
}
nsecs_t c0 = systemTime(timeMode);
- sent = send(clientSock, str.c_str(), str.length(), 0);
+ sent = send(clientSock, str.data(), str.length(), 0);
float t = (float)ns2ms(systemTime(timeMode) - c0);
if (sent != str.length()) {
LOGD("actual sent=%d expected=%d clientSock=%d", sent, str.length(), clientSock);
Die("Failed to send message");
}
-
+ // TODO: factor Receive & TryReceive out and into MessageLoop, or add control argument.
+ // mean while, if server is sending a SETPROP then don't try to receive,
+ // because server will not be processing received command
+ if (msg.function() == msg.SETPROP)
+ return t;
// try to receive commands even though not expecting response,
- // since client can send SETPROP commands anytime
+ // since client can send SETPROP and other commands anytime
if (!msg.expect_response()) {
if (TryReceive(cmd)) {
- LOGD("Send: TryReceived");
if (glesv2debugger::Message_Function_SETPROP == cmd.function())
- LOGD("Send: received SETPROP");
+ LOGD("Send: TryReceived SETPROP");
else
- LOGD("Send: received something else");
+ LOGD("Send: TryReceived %u", cmd.function());
}
} else
Receive(cmd);
-
- pthread_mutex_unlock(&mutex);
return t;
}
void SetProp(DbgContext * const dbg, const glesv2debugger::Message & cmd)
{
switch (cmd.prop()) {
- case glesv2debugger::Message_Prop_Capture:
- LOGD("SetProp Message_Prop_Capture %d", cmd.arg0());
- capture = cmd.arg0();
+ case glesv2debugger::Message_Prop_CaptureDraw:
+ LOGD("SetProp Message_Prop_CaptureDraw %d", cmd.arg0());
+ dbg->captureDraw = cmd.arg0();
break;
case glesv2debugger::Message_Prop_TimeMode:
LOGD("SetProp Message_Prop_TimeMode %d", cmd.arg0());
@@ -200,6 +234,10 @@
LOGD("SetProp Message_Prop_ExpectResponse %d=%d", cmd.arg0(), cmd.arg1());
dbg->expectResponse.Bit((glesv2debugger::Message_Function)cmd.arg0(), cmd.arg1());
break;
+ case glesv2debugger::Message_Prop_CaptureSwap:
+ LOGD("SetProp CaptureSwap %d", cmd.arg0());
+ dbg->captureSwap = cmd.arg0();
+ break;
default:
assert(0);
}
@@ -213,12 +251,17 @@
glesv2debugger::Message cmd;
msg.set_context_id(reinterpret_cast<int>(dbg));
msg.set_type(glesv2debugger::Message_Type_BeforeCall);
- const bool expectResponse = dbg->expectResponse.Bit(function);
+ bool expectResponse = dbg->expectResponse.Bit(function);
msg.set_expect_response(expectResponse);
msg.set_function(function);
- if (!expectResponse)
- cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+
+ // when not exectResponse, set cmd to CONTINUE then SKIP
+ // cmd will be overwritten by received command
+ cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+ cmd.set_expect_response(expectResponse);
+ glesv2debugger::Message_Function oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
while (true) {
msg.Clear();
nsecs_t c0 = systemTime(timeMode);
@@ -233,22 +276,34 @@
msg.set_function(function);
msg.set_type(glesv2debugger::Message_Type_AfterCall);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(glesv2debugger::Message_Function_SKIP);
+ cmd.set_expect_response(false);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
break;
case glesv2debugger::Message_Function_SKIP:
return const_cast<int *>(ret);
case glesv2debugger::Message_Function_SETPROP:
SetProp(dbg, cmd);
- Receive(cmd);
+ expectResponse = cmd.expect_response();
+ if (!expectResponse) // SETPROP is "out of band"
+ cmd.set_function(oldCmd);
+ else
+ Receive(cmd);
break;
default:
ret = GenerateCall(dbg, cmd, msg, ret);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(cmd.SKIP);
+ cmd.set_expect_response(expectResponse);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
break;
}
}
diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp
index 471e5ad..029ee3b 100644
--- a/opengl/libs/GLES2_dbg/src/vertex.cpp
+++ b/opengl/libs/GLES2_dbg/src/vertex.cpp
@@ -21,74 +21,13 @@
bool capture; // capture after each glDraw*
}
-void Debug_glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid* pixels)
-{
- DbgContext * const dbg = getDbgContextThreadSpecific();
- glesv2debugger::Message msg, cmd;
- msg.set_context_id(reinterpret_cast<int>(dbg));
- msg.set_type(glesv2debugger::Message_Type_BeforeCall);
- const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glReadPixels);
- msg.set_expect_response(expectResponse);
- msg.set_function(glesv2debugger::Message_Function_glReadPixels);
- msg.set_arg0(x);
- msg.set_arg1(y);
- msg.set_arg2(width);
- msg.set_arg3(height);
- msg.set_arg4(format);
- msg.set_arg5(type);
- msg.set_arg6(reinterpret_cast<int>(pixels));
-
- const unsigned size = width * height * GetBytesPerPixel(format, type);
- if (!expectResponse)
- cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
- Send(msg, cmd);
- float t = 0;
- while (true) {
- msg.Clear();
- nsecs_t c0 = systemTime(timeMode);
- switch (cmd.function()) {
- case glesv2debugger::Message_Function_CONTINUE:
- dbg->hooks->gl.glReadPixels(x, y, width, height, format, type, pixels);
- msg.set_time((systemTime(timeMode) - c0) * 1e-6f);
- msg.set_context_id(reinterpret_cast<int>(dbg));
- msg.set_function(glesv2debugger::Message_Function_glReadPixels);
- msg.set_type(glesv2debugger::Message_Type_AfterCall);
- msg.set_expect_response(expectResponse);
- if (dbg->IsReadPixelBuffer(pixels)) {
- dbg->CompressReadPixelBuffer(msg.mutable_data());
- msg.set_data_type(msg.ReferencedImage);
- } else {
- dbg->Compress(pixels, size, msg.mutable_data());
- msg.set_data_type(msg.NonreferencedImage);
- }
- if (!expectResponse)
- cmd.set_function(glesv2debugger::Message_Function_SKIP);
- Send(msg, cmd);
- break;
- case glesv2debugger::Message_Function_SKIP:
- return;
- case glesv2debugger::Message_Function_SETPROP:
- SetProp(dbg, cmd);
- Receive(cmd);
- break;
- default:
- GenerateCall(dbg, cmd, msg, NULL);
- msg.set_expect_response(expectResponse);
- if (!expectResponse)
- cmd.set_function(cmd.SKIP);
- Send(msg, cmd);
- break;
- }
- }
-}
-
void Debug_glDrawArrays(GLenum mode, GLint first, GLsizei count)
{
DbgContext * const dbg = getDbgContextThreadSpecific();
glesv2debugger::Message msg, cmd;
msg.set_context_id(reinterpret_cast<int>(dbg));
msg.set_type(glesv2debugger::Message_Type_BeforeCall);
- const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawArrays);
+ bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawArrays);
msg.set_expect_response(expectResponse);
msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
msg.set_arg0(mode);
@@ -103,11 +42,12 @@
}
void * pixels = NULL;
- GLint readFormat = 0, readType = 0;
int viewport[4] = {};
- if (!expectResponse)
- cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+ cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+ cmd.set_expect_response(expectResponse);
+ glesv2debugger::Message_Function oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
while (true) {
msg.Clear();
nsecs_t c0 = systemTime(timeMode);
@@ -119,33 +59,47 @@
msg.set_function(glesv2debugger::Message_Function_glDrawArrays);
msg.set_type(glesv2debugger::Message_Type_AfterCall);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(glesv2debugger::Message_Function_SKIP);
+ cmd.set_expect_response(false);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
- if (capture) {
+ expectResponse = cmd.expect_response();
+ // TODO: pack glReadPixels data with vertex data instead of
+ // relying on sperate call for transport, this would allow
+ // auto generated message loop using EXTEND_Debug macro
+ if (dbg->captureDraw > 0) {
+ dbg->captureDraw--;
dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
// LOGD("glDrawArrays CAPTURE: x=%d y=%d width=%d height=%d format=0x%.4X type=0x%.4X",
// viewport[0], viewport[1], viewport[2], viewport[3], readFormat, readType);
pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
- GetBytesPerPixel(readFormat, readType));
+ dbg->readBytesPerPixel);
Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
- readFormat, readType, pixels);
+ dbg->readFormat, dbg->readType, pixels);
}
break;
case glesv2debugger::Message_Function_SKIP:
return;
case glesv2debugger::Message_Function_SETPROP:
SetProp(dbg, cmd);
- Receive(cmd);
+ expectResponse = cmd.expect_response();
+ if (!expectResponse) // SETPROP is "out of band"
+ cmd.set_function(oldCmd);
+ else
+ Receive(cmd);
break;
default:
GenerateCall(dbg, cmd, msg, NULL);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(cmd.SKIP);
+ cmd.set_expect_response(expectResponse);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
break;
}
}
@@ -169,7 +123,7 @@
glesv2debugger::Message msg, cmd;
msg.set_context_id(reinterpret_cast<int>(dbg));
msg.set_type(glesv2debugger::Message_Type_BeforeCall);
- const bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawElements);
+ bool expectResponse = dbg->expectResponse.Bit(glesv2debugger::Message_Function_glDrawElements);
msg.set_expect_response(expectResponse);
msg.set_function(glesv2debugger::Message_Function_glDrawElements);
msg.set_arg0(mode);
@@ -195,11 +149,12 @@
assert(0);
void * pixels = NULL;
- GLint readFormat = 0, readType = 0;
int viewport[4] = {};
- if (!expectResponse)
- cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+ cmd.set_function(glesv2debugger::Message_Function_CONTINUE);
+ cmd.set_expect_response(expectResponse);
+ glesv2debugger::Message_Function oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
while (true) {
msg.Clear();
nsecs_t c0 = systemTime(timeMode);
@@ -211,33 +166,45 @@
msg.set_function(glesv2debugger::Message_Function_glDrawElements);
msg.set_type(glesv2debugger::Message_Type_AfterCall);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(glesv2debugger::Message_Function_SKIP);
+ cmd.set_expect_response(false);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
- if (capture) {
+ expectResponse = cmd.expect_response();
+ // TODO: pack glReadPixels data with vertex data instead of
+ // relying on sperate call for transport, this would allow
+ // auto generated message loop using EXTEND_Debug macro
+ if (dbg->captureDraw > 0) {
+ dbg->captureDraw--;
dbg->hooks->gl.glGetIntegerv(GL_VIEWPORT, viewport);
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &readFormat);
- dbg->hooks->gl.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &readType);
-// LOGD("glDrawArrays CAPTURE: x=%d y=%d width=%d height=%d format=0x%.4X type=0x%.4X",
-// viewport[0], viewport[1], viewport[2], viewport[3], readFormat, readType);
pixels = dbg->GetReadPixelsBuffer(viewport[2] * viewport[3] *
- GetBytesPerPixel(readFormat, readType));
+ dbg->readBytesPerPixel);
Debug_glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3],
- readFormat, readType, pixels);
+ dbg->readFormat, dbg->readType, pixels);
}
break;
case glesv2debugger::Message_Function_SKIP:
return;
case glesv2debugger::Message_Function_SETPROP:
SetProp(dbg, cmd);
- Receive(cmd);
+ expectResponse = cmd.expect_response();
+ if (!expectResponse) // SETPROP is "out of band"
+ cmd.set_function(oldCmd);
+ else
+ Receive(cmd);
break;
default:
GenerateCall(dbg, cmd, msg, NULL);
msg.set_expect_response(expectResponse);
- if (!expectResponse)
+ if (!expectResponse) {
cmd.set_function(cmd.SKIP);
+ cmd.set_expect_response(expectResponse);
+ }
+ oldCmd = cmd.function();
Send(msg, cmd);
+ expectResponse = cmd.expect_response();
break;
}
}
diff --git a/opengl/libs/GLES2_dbg/test/Android.mk b/opengl/libs/GLES2_dbg/test/Android.mk
new file mode 100644
index 0000000..14a84b4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/Android.mk
@@ -0,0 +1,39 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH) \
+ $(LOCAL_PATH)/../src \
+ $(LOCAL_PATH)/../../ \
+ external/gtest/include \
+ external/stlport/stlport \
+ external/protobuf/src \
+ bionic \
+ external \
+#
+
+LOCAL_SRC_FILES:= \
+ test_main.cpp \
+ test_server.cpp \
+ test_socket.cpp \
+#
+
+LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2_dbg libstlport
+LOCAL_STATIC_LIBRARIES := libgtest libprotobuf-cpp-2.3.0-lite liblzf
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE:= libGLESv2_dbg_test
+
+ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true)
+ LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER
+endif
+ifneq ($(TARGET_SIMULATOR),true)
+ LOCAL_C_INCLUDES += bionic/libc/private
+endif
+
+LOCAL_CFLAGS += -DLOG_TAG=\"libEGL\"
+LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES
+LOCAL_CFLAGS += -fvisibility=hidden
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/opengl/libs/GLES2_dbg/test/test_main.cpp b/opengl/libs/GLES2_dbg/test/test_main.cpp
new file mode 100644
index 0000000..058bea4
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_main.cpp
@@ -0,0 +1,234 @@
+/*
+ ** 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.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "hooks.h"
+
+namespace
+{
+
+// The fixture for testing class Foo.
+class DbgContextTest : public ::testing::Test
+{
+protected:
+ android::DbgContext dbg;
+ gl_hooks_t hooks;
+
+ DbgContextTest()
+ : dbg(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE) {
+ // You can do set-up work for each test here.
+ hooks.gl.glGetError = GetError;
+ }
+
+ static GLenum GetError() {
+ return GL_NO_ERROR;
+ }
+
+ virtual ~DbgContextTest() {
+ // You can do clean-up work that doesn't throw exceptions here.
+ }
+
+ // If the constructor and destructor are not enough for setting up
+ // and cleaning up each test, you can define the following methods:
+
+ virtual void SetUp() {
+ // Code here will be called immediately after the constructor (right
+ // before each test).
+ }
+
+ virtual void TearDown() {
+ // Code here will be called immediately after each test (right
+ // before the destructor).
+ }
+};
+
+TEST_F(DbgContextTest, GetReadPixelBuffer)
+{
+ const unsigned int bufferSize = 512;
+ // test that it's allocating two buffers and swapping them
+ void * const buffer0 = dbg.GetReadPixelsBuffer(bufferSize);
+ ASSERT_NE((void *)NULL, buffer0);
+ for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+ EXPECT_EQ(0, ((unsigned int *)buffer0)[i])
+ << "GetReadPixelsBuffer should allocate and zero";
+ ((unsigned int *)buffer0)[i] = i * 13;
+ }
+
+ void * const buffer1 = dbg.GetReadPixelsBuffer(bufferSize);
+ ASSERT_NE((void *)NULL, buffer1);
+ EXPECT_NE(buffer0, buffer1);
+ for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) {
+ EXPECT_EQ(0, ((unsigned int *)buffer1)[i])
+ << "GetReadPixelsBuffer should allocate and zero";
+ ((unsigned int *)buffer1)[i] = i * 17;
+ }
+
+ void * const buffer2 = dbg.GetReadPixelsBuffer(bufferSize);
+ EXPECT_EQ(buffer2, buffer0);
+ for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+ EXPECT_EQ(i * 13, ((unsigned int *)buffer2)[i])
+ << "GetReadPixelsBuffer should swap buffers";
+
+ void * const buffer3 = dbg.GetReadPixelsBuffer(bufferSize);
+ EXPECT_EQ(buffer3, buffer1);
+ for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++)
+ EXPECT_EQ(i * 17, ((unsigned int *)buffer3)[i])
+ << "GetReadPixelsBuffer should swap buffers";
+
+ void * const buffer4 = dbg.GetReadPixelsBuffer(bufferSize);
+ EXPECT_NE(buffer3, buffer4);
+ EXPECT_EQ(buffer0, buffer2);
+ EXPECT_EQ(buffer1, buffer3);
+ EXPECT_EQ(buffer2, buffer4);
+
+ // it reallocs as necessary; 0 size may result in NULL
+ for (unsigned int i = 0; i < 42; i++) {
+ void * const buffer = dbg.GetReadPixelsBuffer(((i & 7)) << 20);
+ EXPECT_NE((void *)NULL, buffer)
+ << "should be able to get a variety of reasonable sizes";
+ EXPECT_TRUE(dbg.IsReadPixelBuffer(buffer));
+ }
+}
+
+TEST_F(DbgContextTest, CompressReadPixelBuffer)
+{
+ const unsigned int bufferSize = dbg.LZF_CHUNK_SIZE * 4 + 33;
+ std::string out;
+ unsigned char * buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ buffer[i] = i * 13;
+ dbg.CompressReadPixelBuffer(&out);
+ uint32_t decompSize = 0;
+ ASSERT_LT(12, out.length()); // at least written chunk header
+ ASSERT_EQ(bufferSize, *(uint32_t *)out.data())
+ << "total decompressed size should be as requested in GetReadPixelsBuffer";
+ for (unsigned int i = 4; i < out.length();) {
+ const uint32_t outSize = *(uint32_t *)(out.data() + i);
+ i += 4;
+ const uint32_t inSize = *(uint32_t *)(out.data() + i);
+ i += 4;
+ if (inSize == 0)
+ i += outSize; // chunk not compressed
+ else
+ i += inSize; // skip the actual compressed chunk
+ decompSize += outSize;
+ }
+ ASSERT_EQ(bufferSize, decompSize);
+ decompSize = 0;
+
+ unsigned char * decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+ ASSERT_EQ(decompSize, bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ EXPECT_EQ((unsigned char)(i * 13), decomp[i]) << "xor with 0 ref is identity";
+ free(decomp);
+
+ buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ buffer[i] = i * 13;
+ out.clear();
+ dbg.CompressReadPixelBuffer(&out);
+ decompSize = 0;
+ decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+ ASSERT_EQ(decompSize, bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ EXPECT_EQ(0, decomp[i]) << "xor with same ref is 0";
+ free(decomp);
+
+ buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ buffer[i] = i * 19;
+ out.clear();
+ dbg.CompressReadPixelBuffer(&out);
+ decompSize = 0;
+ decomp = dbg.Decompress(out.data(), out.length(), &decompSize);
+ ASSERT_EQ(decompSize, bufferSize);
+ for (unsigned int i = 0; i < bufferSize; i++)
+ EXPECT_EQ((unsigned char)(i * 13) ^ (unsigned char)(i * 19), decomp[i])
+ << "xor ref";
+ free(decomp);
+}
+
+TEST_F(DbgContextTest, UseProgram)
+{
+ static const GLuint _program = 74568;
+ static const struct Attribute {
+ const char * name;
+ GLint location;
+ GLint size;
+ GLenum type;
+ } _attributes [] = {
+ {"aaa", 2, 2, GL_FLOAT_VEC2},
+ {"bb", 6, 2, GL_FLOAT_MAT2},
+ {"c", 1, 1, GL_FLOAT},
+ };
+ static const unsigned int _attributeCount = sizeof(_attributes) / sizeof(*_attributes);
+ struct GL {
+ static void GetProgramiv(GLuint program, GLenum pname, GLint* params) {
+ EXPECT_EQ(_program, program);
+ ASSERT_NE((GLint *)NULL, params);
+ switch (pname) {
+ case GL_ACTIVE_ATTRIBUTES:
+ *params = _attributeCount;
+ return;
+ case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH:
+ *params = 4; // includes NULL terminator
+ return;
+ default:
+ ADD_FAILURE() << "not handled pname: " << pname;
+ }
+ }
+
+ static GLint GetAttribLocation(GLuint program, const GLchar* name) {
+ EXPECT_EQ(_program, program);
+ for (unsigned int i = 0; i < _attributeCount; i++)
+ if (!strcmp(name, _attributes[i].name))
+ return _attributes[i].location;
+ ADD_FAILURE() << "unknown attribute name: " << name;
+ return -1;
+ }
+
+ static void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize,
+ GLsizei* length, GLint* size, GLenum* type, GLchar* name) {
+ EXPECT_EQ(_program, program);
+ ASSERT_LT(index, _attributeCount);
+ const Attribute & att = _attributes[index];
+ ASSERT_GE(bufsize, strlen(att.name) + 1);
+ ASSERT_NE((GLint *)NULL, size);
+ ASSERT_NE((GLenum *)NULL, type);
+ ASSERT_NE((GLchar *)NULL, name);
+ strcpy(name, att.name);
+ if (length)
+ *length = strlen(name) + 1;
+ *size = att.size;
+ *type = att.type;
+ }
+ };
+ hooks.gl.glGetProgramiv = GL::GetProgramiv;
+ hooks.gl.glGetAttribLocation = GL::GetAttribLocation;
+ hooks.gl.glGetActiveAttrib = GL::GetActiveAttrib;
+ dbg.glUseProgram(_program);
+ EXPECT_EQ(10, dbg.maxAttrib);
+ dbg.glUseProgram(0);
+ EXPECT_EQ(0, dbg.maxAttrib);
+}
+} // namespace
+
+int main(int argc, char **argv)
+{
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_server.cpp b/opengl/libs/GLES2_dbg/test/test_server.cpp
new file mode 100644
index 0000000..b6401e0
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_server.cpp
@@ -0,0 +1,251 @@
+/*
+ ** 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.
+ */
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern FILE * file;
+extern unsigned int MAX_FILE_SIZE;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+// tmpfile fails, so need to manually make a writable file first
+static const char * filePath = "/data/local/tmp/dump.gles2dbg";
+
+class ServerFileTest : public ::testing::Test
+{
+protected:
+ ServerFileTest() { }
+
+ virtual ~ServerFileTest() { }
+
+ virtual void SetUp() {
+ MAX_FILE_SIZE = 8 << 20;
+ ASSERT_EQ((FILE *)NULL, file);
+ file = fopen("/data/local/tmp/dump.gles2dbg", "wb+");
+ ASSERT_NE((FILE *)NULL, file) << "make sure file is writable: "
+ << filePath;
+ }
+
+ virtual void TearDown() {
+ ASSERT_NE((FILE *)NULL, file);
+ fclose(file);
+ file = NULL;
+ }
+
+ void Read(glesv2debugger::Message & msg) const {
+ msg.Clear();
+ uint32_t len = 0;
+ ASSERT_EQ(sizeof(len), fread(&len, 1, sizeof(len), file));
+ ASSERT_GT(len, 0u);
+ char * buffer = new char [len];
+ ASSERT_EQ(len, fread(buffer, 1, len, file));
+ msg.ParseFromArray(buffer, len);
+ delete buffer;
+ }
+
+ void CheckNoAvailable() {
+ const long pos = ftell(file);
+ fseek(file, 0, SEEK_END);
+ EXPECT_EQ(pos, ftell(file)) << "check no available";
+ }
+};
+
+TEST_F(ServerFileTest, Send)
+{
+ glesv2debugger::Message msg, cmd, read;
+ msg.set_context_id(1);
+ msg.set_function(msg.glFinish);
+ msg.set_expect_response(false);
+ msg.set_type(msg.BeforeCall);
+ rewind(file);
+ android::Send(msg, cmd);
+ rewind(file);
+ Read(read);
+ EXPECT_EQ(msg.context_id(), read.context_id());
+ EXPECT_EQ(msg.function(), read.function());
+ EXPECT_EQ(msg.expect_response(), read.expect_response());
+ EXPECT_EQ(msg.type(), read.type());
+}
+
+TEST_F(ServerFileTest, CreateDbgContext)
+{
+ gl_hooks_t hooks;
+ struct Constant {
+ GLenum pname;
+ GLint param;
+ };
+ static const Constant constants [] = {
+ {GL_MAX_VERTEX_ATTRIBS, 16},
+ {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, 32},
+ {GL_IMPLEMENTATION_COLOR_READ_FORMAT, GL_RGBA},
+ {GL_IMPLEMENTATION_COLOR_READ_TYPE, GL_UNSIGNED_BYTE},
+ };
+ struct HookMock {
+ static void GetIntegerv(GLenum pname, GLint* params) {
+ ASSERT_TRUE(params != NULL);
+ for (unsigned int i = 0; i < sizeof(constants) / sizeof(*constants); i++)
+ if (pname == constants[i].pname) {
+ *params = constants[i].param;
+ return;
+ }
+ FAIL() << "GetIntegerv unknown pname: " << pname;
+ }
+ static GLenum GetError() {
+ return GL_NO_ERROR;
+ }
+ };
+ hooks.gl.glGetError = HookMock::GetError;
+ hooks.gl.glGetIntegerv = HookMock::GetIntegerv;
+ DbgContext * const dbg = CreateDbgContext(-1, 1, &hooks);
+ ASSERT_TRUE(dbg != NULL);
+ EXPECT_TRUE(dbg->vertexAttribs != NULL);
+
+ rewind(file);
+ glesv2debugger::Message read;
+ for (unsigned int i = 0; i < 2; i++) {
+ Read(read);
+ EXPECT_EQ(reinterpret_cast<int>(dbg), read.context_id());
+ EXPECT_FALSE(read.expect_response());
+ EXPECT_EQ(read.Response, read.type());
+ EXPECT_EQ(read.SETPROP, read.function());
+ EXPECT_EQ(read.GLConstant, read.prop());
+ GLint expectedConstant = 0;
+ HookMock::GetIntegerv(read.arg0(), &expectedConstant);
+ EXPECT_EQ(expectedConstant, read.arg1());
+ }
+ CheckNoAvailable();
+ DestroyDbgContext(dbg);
+}
+
+void * glNoop()
+{
+ return 0;
+}
+
+class ServerFileContextTest : public ServerFileTest
+{
+protected:
+ tls_t tls;
+ gl_hooks_t hooks;
+
+ ServerFileContextTest() { }
+
+ virtual ~ServerFileContextTest() { }
+
+ virtual void SetUp() {
+ ServerFileTest::SetUp();
+
+ if (dbgEGLThreadLocalStorageKey == -1)
+ pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+ ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+ tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+ ASSERT_NE((void *)NULL, tls.dbg);
+ pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+ for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+ ((void **)&hooks)[i] = reinterpret_cast<void *>(glNoop);
+ }
+
+ virtual void TearDown() {
+ ServerFileTest::TearDown();
+ }
+};
+
+TEST_F(ServerFileContextTest, MessageLoop)
+{
+ static const int arg0 = 45;
+ static const float arg7 = -87.2331f;
+ static const int arg8 = -3;
+ static const int * ret = reinterpret_cast<int *>(870);
+
+ struct Caller : public FunctionCall {
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ msg.set_arg0(arg0);
+ msg.set_arg7((int &)arg7);
+ msg.set_arg8(arg8);
+ return ret;
+ }
+ } caller;
+ const int contextId = reinterpret_cast<int>(tls.dbg);
+ glesv2debugger::Message msg, read;
+
+ EXPECT_EQ(ret, MessageLoop(caller, msg, msg.glFinish));
+
+ rewind(file);
+ Read(read);
+ EXPECT_EQ(contextId, read.context_id());
+ EXPECT_EQ(read.glFinish, read.function());
+ EXPECT_EQ(false, read.expect_response());
+ EXPECT_EQ(read.BeforeCall, read.type());
+
+ Read(read);
+ EXPECT_EQ(contextId, read.context_id());
+ EXPECT_EQ(read.glFinish, read.function());
+ EXPECT_EQ(false, read.expect_response());
+ EXPECT_EQ(read.AfterCall, read.type());
+ EXPECT_TRUE(read.has_time());
+ EXPECT_EQ(arg0, read.arg0());
+ const int readArg7 = read.arg7();
+ EXPECT_EQ(arg7, (float &)readArg7);
+ EXPECT_EQ(arg8, read.arg8());
+
+ const long pos = ftell(file);
+ fseek(file, 0, SEEK_END);
+ EXPECT_EQ(pos, ftell(file))
+ << "should only write the BeforeCall and AfterCall messages";
+}
+
+TEST_F(ServerFileContextTest, DisableEnableVertexAttribArray)
+{
+ Debug_glEnableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 2); // should just ignore invalid index
+
+ glesv2debugger::Message read;
+ rewind(file);
+ Read(read);
+ EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+ EXPECT_EQ(tls.dbg->MAX_VERTEX_ATTRIBS + 2, read.arg0());
+ Read(read);
+
+ rewind(file);
+ Debug_glDisableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 4); // should just ignore invalid index
+ rewind(file);
+ Read(read);
+ Read(read);
+
+ for (unsigned int i = 0; i < tls.dbg->MAX_VERTEX_ATTRIBS; i += 5) {
+ rewind(file);
+ Debug_glEnableVertexAttribArray(i);
+ EXPECT_TRUE(tls.dbg->vertexAttribs[i].enabled);
+ rewind(file);
+ Read(read);
+ EXPECT_EQ(read.glEnableVertexAttribArray, read.function());
+ EXPECT_EQ(i, read.arg0());
+ Read(read);
+
+ rewind(file);
+ Debug_glDisableVertexAttribArray(i);
+ EXPECT_FALSE(tls.dbg->vertexAttribs[i].enabled);
+ rewind(file);
+ Read(read);
+ EXPECT_EQ(read.glDisableVertexAttribArray, read.function());
+ EXPECT_EQ(i, read.arg0());
+ Read(read);
+ }
+}
diff --git a/opengl/libs/GLES2_dbg/test/test_socket.cpp b/opengl/libs/GLES2_dbg/test/test_socket.cpp
new file mode 100644
index 0000000..617292e
--- /dev/null
+++ b/opengl/libs/GLES2_dbg/test/test_socket.cpp
@@ -0,0 +1,474 @@
+/*
+ ** 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.
+ */
+
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+
+#include "header.h"
+#include "gtest/gtest.h"
+#include "egl_tls.h"
+#include "hooks.h"
+
+namespace android
+{
+extern int serverSock, clientSock;
+extern pthread_key_t dbgEGLThreadLocalStorageKey;
+};
+
+void * glNoop();
+
+class SocketContextTest : public ::testing::Test
+{
+protected:
+ tls_t tls;
+ gl_hooks_t hooks;
+ int sock;
+ char * buffer;
+ unsigned int bufferSize;
+
+ SocketContextTest() : sock(-1) {
+ }
+
+ virtual ~SocketContextTest() {
+ }
+
+ virtual void SetUp() {
+ if (dbgEGLThreadLocalStorageKey == -1)
+ pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL);
+ ASSERT_NE(-1, dbgEGLThreadLocalStorageKey);
+ tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE);
+ ASSERT_TRUE(tls.dbg != NULL);
+ pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls);
+ for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
+ ((void **)&hooks)[i] = (void *)glNoop;
+
+ int socks[2] = {-1, -1};
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
+ clientSock = socks[0];
+ sock = socks[1];
+
+ bufferSize = 128;
+ buffer = new char [128];
+ ASSERT_NE((char *)NULL, buffer);
+ }
+
+ virtual void TearDown() {
+ close(sock);
+ close(clientSock);
+ clientSock = -1;
+ delete buffer;
+ }
+
+ void Write(glesv2debugger::Message & msg) const {
+ msg.set_context_id((int)tls.dbg);
+ msg.set_type(msg.Response);
+ ASSERT_TRUE(msg.has_context_id());
+ ASSERT_TRUE(msg.has_function());
+ ASSERT_TRUE(msg.has_type());
+ ASSERT_TRUE(msg.has_expect_response());
+ static std::string str;
+ msg.SerializeToString(&str);
+ const uint32_t len = str.length();
+ ASSERT_EQ(sizeof(len), send(sock, &len, sizeof(len), 0));
+ ASSERT_EQ(str.length(), send(sock, str.data(), str.length(), 0));
+ }
+
+ void Read(glesv2debugger::Message & msg) {
+ int available = 0;
+ ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+ ASSERT_GT(available, 0);
+ uint32_t len = 0;
+ ASSERT_EQ(sizeof(len), recv(sock, &len, sizeof(len), 0));
+ if (len > bufferSize) {
+ bufferSize = len;
+ buffer = new char[bufferSize];
+ ASSERT_TRUE(buffer != NULL);
+ }
+ ASSERT_EQ(len, recv(sock, buffer, len, 0));
+ msg.Clear();
+ msg.ParseFromArray(buffer, len);
+ ASSERT_TRUE(msg.has_context_id());
+ ASSERT_TRUE(msg.has_function());
+ ASSERT_TRUE(msg.has_type());
+ ASSERT_TRUE(msg.has_expect_response());
+ }
+
+ void CheckNoAvailable() {
+ int available = 0;
+ ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
+ ASSERT_EQ(available, 0);
+ }
+};
+
+TEST_F(SocketContextTest, MessageLoopSkip)
+{
+ static const int arg0 = 45;
+ static const float arg7 = -87.2331f;
+ static const int arg8 = -3;
+ static const int * ret = (int *)870;
+
+ struct Caller : public FunctionCall {
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ msg.set_arg0(arg0);
+ msg.set_arg7((int &)arg7);
+ msg.set_arg8(arg8);
+ return ret;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ tls.dbg->expectResponse.Bit(msg.glFinish, true);
+
+ cmd.set_function(cmd.SKIP);
+ cmd.set_expect_response(false);
+ Write(cmd);
+
+ EXPECT_NE(ret, MessageLoop(caller, msg, msg.glFinish));
+
+ Read(read);
+ EXPECT_EQ(read.glFinish, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+ EXPECT_NE(arg0, read.arg0());
+ EXPECT_NE((int &)arg7, read.arg7());
+ EXPECT_NE(arg8, read.arg8());
+
+ CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopContinue)
+{
+ static const int arg0 = GL_FRAGMENT_SHADER;
+ static const int ret = -342;
+ struct Caller : public FunctionCall {
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ msg.set_ret(ret);
+ return (int *)ret;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ tls.dbg->expectResponse.Bit(msg.glCreateShader, true);
+
+ cmd.set_function(cmd.CONTINUE);
+ cmd.set_expect_response(false); // MessageLoop should automatically skip after continue
+ Write(cmd);
+
+ msg.set_arg0(arg0);
+ EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateShader));
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+ EXPECT_EQ(arg0, read.arg0());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.AfterCall, read.type());
+ EXPECT_EQ(ret, read.ret());
+
+ CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopGenerateCall)
+{
+ static const int ret = -342;
+ static unsigned int createShader, createProgram;
+ createShader = 0;
+ createProgram = 0;
+ struct Caller : public FunctionCall {
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ const int r = (int)_c->glCreateProgram();
+ msg.set_ret(r);
+ return (int *)r;
+ }
+ static GLuint CreateShader(const GLenum type) {
+ createShader++;
+ return type;
+ }
+ static GLuint CreateProgram() {
+ createProgram++;
+ return ret;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ hooks.gl.glCreateShader = caller.CreateShader;
+ hooks.gl.glCreateProgram = caller.CreateProgram;
+ tls.dbg->expectResponse.Bit(msg.glCreateProgram, true);
+
+ cmd.set_function(cmd.glCreateShader);
+ cmd.set_arg0(GL_FRAGMENT_SHADER);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.Clear();
+ cmd.set_function(cmd.CONTINUE);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.set_function(cmd.glCreateShader);
+ cmd.set_arg0(GL_VERTEX_SHADER);
+ cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+ Write(cmd);
+
+ EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+ Read(read);
+ EXPECT_EQ(read.glCreateProgram, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.AfterGeneratedCall, read.type());
+ EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateProgram, read.function());
+ EXPECT_EQ(read.AfterCall, read.type());
+ EXPECT_EQ(ret, read.ret());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.AfterGeneratedCall, read.type());
+ EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+ EXPECT_EQ(2, createShader);
+ EXPECT_EQ(1, createProgram);
+
+ CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, MessageLoopSetProp)
+{
+ static const int ret = -342;
+ static unsigned int createShader, createProgram;
+ createShader = 0;
+ createProgram = 0;
+ struct Caller : public FunctionCall {
+ const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
+ const int r = (int)_c->glCreateProgram();
+ msg.set_ret(r);
+ return (int *)r;
+ }
+ static GLuint CreateShader(const GLenum type) {
+ createShader++;
+ return type;
+ }
+ static GLuint CreateProgram() {
+ createProgram++;
+ return ret;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ hooks.gl.glCreateShader = caller.CreateShader;
+ hooks.gl.glCreateProgram = caller.CreateProgram;
+ tls.dbg->expectResponse.Bit(msg.glCreateProgram, false);
+
+ cmd.set_function(cmd.SETPROP);
+ cmd.set_prop(cmd.ExpectResponse);
+ cmd.set_arg0(cmd.glCreateProgram);
+ cmd.set_arg1(true);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.Clear();
+ cmd.set_function(cmd.glCreateShader);
+ cmd.set_arg0(GL_FRAGMENT_SHADER);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.set_function(cmd.SETPROP);
+ cmd.set_prop(cmd.CaptureDraw);
+ cmd.set_arg0(819);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.Clear();
+ cmd.set_function(cmd.CONTINUE);
+ cmd.set_expect_response(true);
+ Write(cmd);
+
+ cmd.set_function(cmd.glCreateShader);
+ cmd.set_arg0(GL_VERTEX_SHADER);
+ cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
+ Write(cmd);
+
+ EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));
+
+ EXPECT_TRUE(tls.dbg->expectResponse.Bit(msg.glCreateProgram));
+ EXPECT_EQ(819, tls.dbg->captureDraw);
+
+ Read(read);
+ EXPECT_EQ(read.glCreateProgram, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.AfterGeneratedCall, read.type());
+ EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateProgram, read.function());
+ EXPECT_EQ(read.AfterCall, read.type());
+ EXPECT_EQ(ret, read.ret());
+
+ Read(read);
+ EXPECT_EQ(read.glCreateShader, read.function());
+ EXPECT_EQ(read.AfterGeneratedCall, read.type());
+ EXPECT_EQ(GL_VERTEX_SHADER, read.ret());
+
+ EXPECT_EQ(2, createShader);
+ EXPECT_EQ(1, createProgram);
+
+ CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, TexImage2D)
+{
+ static const GLenum _target = GL_TEXTURE_2D;
+ static const GLint _level = 1, _internalformat = GL_RGBA;
+ static const GLsizei _width = 2, _height = 2;
+ static const GLint _border = 333;
+ static const GLenum _format = GL_RGB, _type = GL_UNSIGNED_SHORT_5_6_5;
+ static const short _pixels [_width * _height] = {11, 22, 33, 44};
+ static unsigned int texImage2D;
+ texImage2D = 0;
+
+ struct Caller {
+ static void TexImage2D(GLenum target, GLint level, GLint internalformat,
+ GLsizei width, GLsizei height, GLint border,
+ GLenum format, GLenum type, const GLvoid* pixels) {
+ EXPECT_EQ(_target, target);
+ EXPECT_EQ(_level, level);
+ EXPECT_EQ(_internalformat, internalformat);
+ EXPECT_EQ(_width, width);
+ EXPECT_EQ(_height, height);
+ EXPECT_EQ(_border, border);
+ EXPECT_EQ(_format, format);
+ EXPECT_EQ(_type, type);
+ EXPECT_EQ(0, memcmp(_pixels, pixels, sizeof(_pixels)));
+ texImage2D++;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ hooks.gl.glTexImage2D = caller.TexImage2D;
+ tls.dbg->expectResponse.Bit(msg.glTexImage2D, false);
+
+ Debug_glTexImage2D(_target, _level, _internalformat, _width, _height, _border,
+ _format, _type, _pixels);
+ EXPECT_EQ(1, texImage2D);
+
+ Read(read);
+ EXPECT_EQ(read.glTexImage2D, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+ EXPECT_EQ(_target, read.arg0());
+ EXPECT_EQ(_level, read.arg1());
+ EXPECT_EQ(_internalformat, read.arg2());
+ EXPECT_EQ(_width, read.arg3());
+ EXPECT_EQ(_height, read.arg4());
+ EXPECT_EQ(_border, read.arg5());
+ EXPECT_EQ(_format, read.arg6());
+ EXPECT_EQ(_type, read.arg7());
+
+ EXPECT_TRUE(read.has_data());
+ uint32_t dataLen = 0;
+ const unsigned char * data = tls.dbg->Decompress(read.data().data(),
+ read.data().length(), &dataLen);
+ EXPECT_EQ(sizeof(_pixels), dataLen);
+ if (sizeof(_pixels) == dataLen)
+ EXPECT_EQ(0, memcmp(_pixels, data, sizeof(_pixels)));
+
+ Read(read);
+ EXPECT_EQ(read.glTexImage2D, read.function());
+ EXPECT_EQ(read.AfterCall, read.type());
+
+ CheckNoAvailable();
+}
+
+TEST_F(SocketContextTest, CopyTexImage2D)
+{
+ static const GLenum _target = GL_TEXTURE_2D;
+ static const GLint _level = 1, _internalformat = GL_RGBA;
+ static const GLint _x = 9, _y = 99;
+ static const GLsizei _width = 2, _height = 3;
+ static const GLint _border = 333;
+ static const int _pixels [_width * _height] = {11, 22, 33, 44, 55, 66};
+ static unsigned int copyTexImage2D, readPixels;
+ copyTexImage2D = 0, readPixels = 0;
+
+ struct Caller {
+ static void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
+ GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {
+ EXPECT_EQ(_target, target);
+ EXPECT_EQ(_level, level);
+ EXPECT_EQ(_internalformat, internalformat);
+ EXPECT_EQ(_x, x);
+ EXPECT_EQ(_y, y);
+ EXPECT_EQ(_width, width);
+ EXPECT_EQ(_height, height);
+ EXPECT_EQ(_border, border);
+ copyTexImage2D++;
+ }
+ static void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
+ GLenum format, GLenum type, GLvoid* pixels) {
+ EXPECT_EQ(_x, x);
+ EXPECT_EQ(_y, y);
+ EXPECT_EQ(_width, width);
+ EXPECT_EQ(_height, height);
+ EXPECT_EQ(GL_RGBA, format);
+ EXPECT_EQ(GL_UNSIGNED_BYTE, type);
+ ASSERT_TRUE(pixels != NULL);
+ memcpy(pixels, _pixels, sizeof(_pixels));
+ readPixels++;
+ }
+ } caller;
+ glesv2debugger::Message msg, read, cmd;
+ hooks.gl.glCopyTexImage2D = caller.CopyTexImage2D;
+ hooks.gl.glReadPixels = caller.ReadPixels;
+ tls.dbg->expectResponse.Bit(msg.glCopyTexImage2D, false);
+
+ Debug_glCopyTexImage2D(_target, _level, _internalformat, _x, _y, _width, _height,
+ _border);
+ ASSERT_EQ(1, copyTexImage2D);
+ ASSERT_EQ(1, readPixels);
+
+ Read(read);
+ EXPECT_EQ(read.glCopyTexImage2D, read.function());
+ EXPECT_EQ(read.BeforeCall, read.type());
+ EXPECT_EQ(_target, read.arg0());
+ EXPECT_EQ(_level, read.arg1());
+ EXPECT_EQ(_internalformat, read.arg2());
+ EXPECT_EQ(_x, read.arg3());
+ EXPECT_EQ(_y, read.arg4());
+ EXPECT_EQ(_width, read.arg5());
+ EXPECT_EQ(_height, read.arg6());
+ EXPECT_EQ(_border, read.arg7());
+
+ EXPECT_TRUE(read.has_data());
+ EXPECT_EQ(read.ReferencedImage, read.data_type());
+ EXPECT_EQ(GL_RGBA, read.pixel_format());
+ EXPECT_EQ(GL_UNSIGNED_BYTE, read.pixel_type());
+ uint32_t dataLen = 0;
+ unsigned char * const data = tls.dbg->Decompress(read.data().data(),
+ read.data().length(), &dataLen);
+ ASSERT_EQ(sizeof(_pixels), dataLen);
+ for (unsigned i = 0; i < sizeof(_pixels) / sizeof(*_pixels); i++)
+ EXPECT_EQ(_pixels[i], ((const int *)data)[i]) << "xor with 0 ref is identity";
+ free(data);
+
+ Read(read);
+ EXPECT_EQ(read.glCopyTexImage2D, read.function());
+ EXPECT_EQ(read.AfterCall, read.type());
+
+ CheckNoAvailable();
+}
diff --git a/opengl/libs/egl_tls.h b/opengl/libs/egl_tls.h
new file mode 100644
index 0000000..087989a
--- /dev/null
+++ b/opengl/libs/egl_tls.h
@@ -0,0 +1,40 @@
+/*
+ ** 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.
+ */
+
+#ifndef ANDROID_EGL_TLS_H
+#define ANDROID_EGL_TLS_H
+
+#include <EGL/egl.h>
+
+#include "glesv2dbg.h"
+
+namespace android
+{
+struct tls_t {
+ tls_t() : error(EGL_SUCCESS), ctx(0), logCallWithNoContext(EGL_TRUE), dbg(0) { }
+ ~tls_t() {
+ if (dbg)
+ DestroyDbgContext(dbg);
+ }
+
+ EGLint error;
+ EGLContext ctx;
+ EGLBoolean logCallWithNoContext;
+ DbgContext* dbg;
+};
+}
+
+#endif
diff --git a/opengl/libs/glesv2dbg.h b/opengl/libs/glesv2dbg.h
index 8029dce..ee2c011 100644
--- a/opengl/libs/glesv2dbg.h
+++ b/opengl/libs/glesv2dbg.h
@@ -13,20 +13,27 @@
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-
+
#ifndef _GLESV2_DBG_H_
#define _GLESV2_DBG_H_
+#include <pthread.h>
+
namespace android
{
- struct DbgContext;
-
- DbgContext * CreateDbgContext(const unsigned version, const gl_hooks_t * const hooks);
- void DestroyDbgContext(DbgContext * const dbg);
-
- void StartDebugServer(unsigned short port); // create and bind socket if haven't already
- void StopDebugServer(); // close socket if open
-
+struct DbgContext;
+
+DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey,
+ const unsigned version, const gl_hooks_t * const hooks);
+
+void DestroyDbgContext(DbgContext * const dbg);
+
+// create and bind socket if haven't already, if failed to create socket or
+// forceUseFile, then open /data/local/tmp/dump.gles2dbg, exit when size reached
+void StartDebugServer(const unsigned short port, const bool forceUseFile,
+ const unsigned int maxFileSize, const char * const filePath);
+void StopDebugServer(); // close socket if open
+
}; // namespace android
#endif // #ifndef _GLESV2_DBG_H_
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index d901c2c..b99c8b0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -61,7 +61,7 @@
// database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
// is properly propagated through your change. Not doing so will result in a loss of user
// settings.
- private static final int DATABASE_VERSION = 64;
+ private static final int DATABASE_VERSION = 65;
private Context mContext;
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index 9938a8b..879f1a8 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -26,16 +26,18 @@
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
import com.android.internal.view.StandaloneActionMode;
import com.android.internal.view.menu.ActionMenuView;
import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.ListMenuPresenter;
+import com.android.internal.view.menu.IconMenuPresenter;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuDialogHelper;
import com.android.internal.view.menu.MenuItemImpl;
import com.android.internal.view.menu.MenuPopupHelper;
import com.android.internal.view.menu.MenuView;
+import com.android.internal.view.menu.MenuPresenter;
import com.android.internal.view.menu.SubMenuBuilder;
import com.android.internal.widget.ActionBarContextView;
import com.android.internal.widget.ActionBarView;
@@ -75,7 +77,6 @@
import android.view.ViewStub;
import android.view.Window;
import android.view.WindowManager;
-import android.view.View.MeasureSpec;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
@@ -125,6 +126,8 @@
private TextView mTitleView;
private ActionBarView mActionBar;
+ private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+ private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
private DrawableFeatureState[] mDrawables;
@@ -167,7 +170,6 @@
private ContextMenuBuilder mContextMenu;
private MenuDialogHelper mContextMenuHelper;
- private ActionButtonSubmenu mActionButtonPopup;
private boolean mClosingActionMenu;
private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
@@ -342,7 +344,12 @@
return false;
}
}
- // Call callback, and return if it doesn't want to display menu
+
+ // Call callback, and return if it doesn't want to display menu.
+
+ // Creating the panel menu will involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
// Ditch the menu created above
st.menu = null;
@@ -353,14 +360,23 @@
st.refreshMenuContent = false;
if (mActionBar != null) {
- mActionBar.setMenu(st.menu);
+ if (mActionMenuPresenterCallback == null) {
+ mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+ }
+ mActionBar.setMenu(st.menu, mActionMenuPresenterCallback);
}
}
// Callback and return if the callback does not want to show the menu
+
+ // Preparing the panel menu can involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
+ st.menu.startDispatchingItemsChanged();
return false;
}
+ st.menu.startDispatchingItemsChanged();
// Set the proper keymap
KeyCharacterMap kmap = KeyCharacterMap.load(
@@ -383,12 +399,15 @@
if (mActionBar == null) {
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if ((st != null) && (st.menu != null)) {
- final MenuBuilder menuBuilder = (MenuBuilder) st.menu;
-
if (st.isOpen) {
// Freeze state
final Bundle state = new Bundle();
- menuBuilder.saveHierarchyState(state);
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.saveHierarchyState(state);
+ }
+ if (st.expandedMenuPresenter != null) {
+ st.expandedMenuPresenter.saveHierarchyState(state);
+ }
// Remove the menu views since they need to be recreated
// according to the new configuration
@@ -398,7 +417,12 @@
reopenMenu(false);
// Restore state
- menuBuilder.restoreHierarchyState(state);
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.restoreHierarchyState(state);
+ }
+ if (st.expandedMenuPresenter != null) {
+ st.expandedMenuPresenter.restoreHierarchyState(state);
+ }
} else {
// Clear menu views so on next menu opening, it will use
@@ -418,8 +442,8 @@
// Causes the decor view to be recreated
st.refreshDecorView = true;
-
- ((MenuBuilder) st.menu).clearMenuViews();
+
+ st.clearMenuPresenters();
}
@Override
@@ -563,6 +587,12 @@
*/
public final void closePanel(PanelFeatureState st, boolean doCallback) {
// System.out.println("Close panel: isOpen=" + st.isOpen);
+ if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
+ mActionBar != null && mActionBar.isOverflowMenuShowing()) {
+ checkCloseActionMenu(st.menu);
+ return;
+ }
+
final ViewManager wm = getWindowManager();
if ((wm != null) && st.isOpen) {
if (st.decorView != null) {
@@ -573,10 +603,8 @@
if (doCallback) {
callOnPanelClosed(st.featureId, st, null);
}
- } else if (st.featureId == FEATURE_OPTIONS_PANEL && doCallback &&
- mActionBar != null) {
- checkCloseActionMenu(st.menu);
}
+
st.isPrepared = false;
st.isHandled = false;
st.isOpen = false;
@@ -602,17 +630,10 @@
return;
}
- boolean closed = false;
mClosingActionMenu = true;
- if (mActionBar.isOverflowMenuOpen() && mActionBar.hideOverflowMenu()) {
- closed = true;
- }
- if (mActionButtonPopup != null) {
- mActionButtonPopup.dismiss();
- closed = true;
- }
+ mActionBar.dismissPopupMenus();
Callback cb = getCallback();
- if (cb != null && closed && !isDestroyed()) {
+ if (cb != null && !isDestroyed()) {
cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
}
mClosingActionMenu = false;
@@ -849,54 +870,6 @@
return false;
}
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- final PanelFeatureState panel = findMenuPanel(menu);
- if (panel != null) {
- // Close the panel and only do the callback if the menu is being
- // closed
- // completely, not if opening a sub menu
- closePanel(panel, allMenusAreClosing);
- }
- }
-
- public void onCloseSubMenu(SubMenuBuilder subMenu) {
- final Menu parentMenu = subMenu.getRootMenu();
- final PanelFeatureState panel = findMenuPanel(parentMenu);
-
- // Callback
- if (panel != null) {
- callOnPanelClosed(panel.featureId, panel, parentMenu);
- closePanel(panel, true);
- }
- }
-
- public boolean onSubMenuSelected(final SubMenuBuilder subMenu) {
- if (!subMenu.hasVisibleItems()) {
- return true;
- }
-
- final Menu parentMenu = subMenu.getRootMenu();
- final PanelFeatureState panel = findMenuPanel(parentMenu);
-
- if (hasFeature(FEATURE_ACTION_BAR) && panel.featureId == FEATURE_OPTIONS_PANEL) {
- mDecor.post(new Runnable() {
- public void run() {
- mActionButtonPopup = new ActionButtonSubmenu(getContext(), subMenu);
- mActionButtonPopup.show();
- Callback cb = getCallback();
- if (cb != null && !isDestroyed()) {
- cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
- }
- }
- });
- } else {
- // The window manager will give us a valid window token
- new MenuDialogHelper(subMenu).show(null);
- }
-
- return true;
- }
-
public void onMenuModeChange(MenuBuilder menu) {
reopenMenu(true);
}
@@ -978,23 +951,28 @@
* @return Whether the initialization was successful.
*/
protected boolean initializePanelContent(PanelFeatureState st) {
-
if (st.createdPanelView != null) {
st.shownPanelView = st.createdPanelView;
return true;
}
- final MenuBuilder menu = (MenuBuilder)st.menu;
- if (menu == null) {
+ if (st.menu == null) {
return false;
}
- st.shownPanelView = menu.getMenuView((st.isInExpandedMode) ? MenuBuilder.TYPE_EXPANDED
- : MenuBuilder.TYPE_ICON, st.decorView);
+ if (mPanelMenuPresenterCallback == null) {
+ mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+ }
+
+ MenuView menuView = st.isInExpandedMode
+ ? st.getExpandedMenuView(mPanelMenuPresenterCallback)
+ : st.getIconMenuView(mPanelMenuPresenterCallback);
+
+ st.shownPanelView = (View) menuView;
if (st.shownPanelView != null) {
// Use the menu View's default animations if it has any
- final int defaultAnimations = ((MenuView) st.shownPanelView).getWindowAnimations();
+ final int defaultAnimations = menuView.getWindowAnimations();
if (defaultAnimations != 0) {
st.windowAnimations = defaultAnimations;
}
@@ -1581,6 +1559,54 @@
}
}
+ private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final Menu parentMenu = menu.getRootMenu();
+ final boolean isSubMenu = parentMenu != menu;
+ final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
+ if (panel != null) {
+ if (isSubMenu) {
+ callOnPanelClosed(panel.featureId, panel, parentMenu);
+ closePanel(panel, true);
+ } else {
+ // Close the panel and only do the callback if the menu is being
+ // closed completely, not if opening a sub menu
+ closePanel(panel, allMenusAreClosing);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) {
+ Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ Callback cb = getCallback();
+ if (cb != null) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ checkCloseActionMenu(menu);
+ }
+ }
+
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
/* package */int mDefaultOpacity = PixelFormat.OPAQUE;
@@ -2190,11 +2216,8 @@
cb.onDetachedFromWindow();
}
- if (mActionButtonPopup != null) {
- if (mActionButtonPopup.isShowing()) {
- mActionButtonPopup.dismiss();
- }
- mActionButtonPopup = null;
+ if (mActionBar != null) {
+ mActionBar.dismissPopupMenus();
}
if (mActionModePopup != null) {
@@ -2207,14 +2230,6 @@
}
@Override
- protected void onConfigurationChanged(Configuration newConfig) {
- if (mActionButtonPopup != null) {
- mActionButtonPopup.dismiss();
- post(mActionButtonPopup);
- }
- }
-
- @Override
public void onCloseSystemDialogs(String reason) {
if (mFeatureId >= 0) {
closeAllPanels();
@@ -2914,7 +2929,10 @@
View shownPanelView;
/** Use {@link #setMenu} to set this. */
- Menu menu;
+ MenuBuilder menu;
+
+ IconMenuPresenter iconMenuPresenter;
+ ListMenuPresenter expandedMenuPresenter;
/**
* Whether the panel has been prepared (see
@@ -2958,6 +2976,18 @@
refreshDecorView = false;
}
+ /**
+ * Unregister and free attached MenuPresenters. They will be recreated as needed.
+ */
+ public void clearMenuPresenters() {
+ if (menu != null) {
+ menu.removeMenuPresenter(iconMenuPresenter);
+ menu.removeMenuPresenter(expandedMenuPresenter);
+ }
+ iconMenuPresenter = null;
+ expandedMenuPresenter = null;
+ }
+
void setStyle(Context context) {
TypedArray a = context.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
background = a.getResourceId(
@@ -2969,13 +2999,56 @@
a.recycle();
}
- void setMenu(Menu menu) {
+ void setMenu(MenuBuilder menu) {
this.menu = menu;
+ }
- if (frozenMenuState != null) {
- ((MenuBuilder) menu).restoreHierarchyState(frozenMenuState);
+ MenuView getExpandedMenuView(MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ getIconMenuView(cb); // Need this initialized to know where our offset goes
+
+ boolean init = false;
+ if (expandedMenuPresenter == null) {
+ expandedMenuPresenter = new ListMenuPresenter(
+ com.android.internal.R.layout.list_menu_item_layout,
+ com.android.internal.R.style.Theme_ExpandedMenu);
+ expandedMenuPresenter.setCallback(cb);
+ menu.addMenuPresenter(expandedMenuPresenter);
+ init = true;
+ }
+
+ expandedMenuPresenter.setItemIndexOffset(iconMenuPresenter.getNumActualItemsShown());
+ MenuView result = expandedMenuPresenter.getMenuView(decorView);
+
+ if (init && frozenMenuState != null) {
+ expandedMenuPresenter.restoreHierarchyState(frozenMenuState);
+ // Once we initialize the expanded menu we're done with the frozen state
+ // since we will have also restored any icon menu state.
frozenMenuState = null;
}
+
+ return result;
+ }
+
+ MenuView getIconMenuView(MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ boolean init = false;
+ if (iconMenuPresenter == null) {
+ iconMenuPresenter = new IconMenuPresenter();
+ iconMenuPresenter.setCallback(cb);
+ menu.addMenuPresenter(iconMenuPresenter);
+ init = true;
+ }
+
+ MenuView result = iconMenuPresenter.getMenuView(decorView);
+
+ if (init && frozenMenuState != null) {
+ iconMenuPresenter.restoreHierarchyState(frozenMenuState);
+ }
+
+ return result;
}
Parcelable onSaveInstanceState() {
@@ -2986,7 +3059,12 @@
if (menu != null) {
savedState.menuState = new Bundle();
- ((MenuBuilder) menu).saveHierarchyState(savedState.menuState);
+ if (iconMenuPresenter != null) {
+ iconMenuPresenter.saveHierarchyState(savedState.menuState);
+ }
+ if (expandedMenuPresenter != null) {
+ expandedMenuPresenter.saveHierarchyState(savedState.menuState);
+ }
}
return savedState;
@@ -3127,44 +3205,4 @@
void sendCloseSystemWindows(String reason) {
PhoneWindowManager.sendCloseSystemWindows(getContext(), reason);
}
-
- private class ActionButtonSubmenu extends MenuPopupHelper implements Runnable {
- private SubMenuBuilder mSubMenu;
-
- public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
- super(context, subMenu);
- mSubMenu = subMenu;
-
- MenuBuilder parentMenu = subMenu.getRootMenu();
- MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
- if (!item.isActionButton()) {
- // Give a reasonable anchor to nested submenus.
- ActionMenuView amv = (ActionMenuView) parentMenu.getMenuView(
- MenuBuilder.TYPE_ACTION_BUTTON, null);
-
- View anchor = amv.getOverflowButton();
- if (anchor == null) {
- anchor = amv;
- }
- setAnchorView(anchor);
- }
- }
-
- @Override
- public void onDismiss() {
- super.onDismiss();
- mSubMenu.getCallback().onCloseSubMenu(mSubMenu);
- mActionButtonPopup = null;
- }
-
- @Override
- public void run() {
- if (tryShow()) {
- Callback cb = getCallback();
- if (cb != null && !isDestroyed()) {
- cb.onMenuOpened(FEATURE_ACTION_BAR, mSubMenu);
- }
- }
- }
- }
}
diff --git a/services/input/Android.mk b/services/input/Android.mk
index f9f8623..836c081 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -23,7 +23,6 @@
InputReader.cpp \
InputWindow.cpp \
PointerController.cpp \
- SpotController.cpp \
SpriteController.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 6db445e..98b3526 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,7 +36,6 @@
// Log debug messages about gesture detection.
#define DEBUG_GESTURES 0
-
#include "InputReader.h"
#include <cutils/log.h>
@@ -71,23 +70,52 @@
// Tap gesture delay time.
// The time between down and up must be less than this to be considered a tap.
-static const nsecs_t TAP_INTERVAL = 100 * 1000000; // 100 ms
+static const nsecs_t TAP_INTERVAL = 150 * 1000000; // 150 ms
+
+// Tap drag gesture delay time.
+// The time between up and the next up must be greater than this to be considered a
+// drag. Otherwise, the previous tap is finished and a new tap begins.
+static const nsecs_t TAP_DRAG_INTERVAL = 150 * 1000000; // 150 ms
// The distance in pixels that the pointer is allowed to move from initial down
// to up and still be called a tap.
-static const float TAP_SLOP = 5.0f; // 5 pixels
+static const float TAP_SLOP = 10.0f; // 10 pixels
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE or FREEFORM gesture mode is made when
-// all of the pointers have traveled this number of pixels from the start point.
-static const float MULTITOUCH_MIN_TRAVEL = 5.0f;
+// Time after the first touch points go down to settle on an initial centroid.
+// This is intended to be enough time to handle cases where the user puts down two
+// fingers at almost but not quite exactly the same time.
+static const nsecs_t MULTITOUCH_SETTLE_INTERVAL = 100 * 1000000; // 100ms
-// The transition from INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the
+// The transition from PRESS to SWIPE or FREEFORM gesture mode is made when
+// both of the pointers are moving at least this fast.
+static const float MULTITOUCH_MIN_SPEED = 150.0f; // pixels per second
+
+// The transition from PRESS to SWIPE gesture mode can only occur when the
// cosine of the angle between the two vectors is greater than or equal to than this value
// which indicates that the vectors are oriented in the same direction.
// When the vectors are oriented in the exactly same direction, the cosine is 1.0.
// (In exactly opposite directions, the cosine is -1.0.)
static const float SWIPE_TRANSITION_ANGLE_COSINE = 0.5f; // cosine of 45 degrees
+// The transition from PRESS to SWIPE gesture mode can only occur when the
+// fingers are no more than this far apart relative to the diagonal size of
+// the touch pad. For example, a ratio of 0.5 means that the fingers must be
+// no more than half the diagonal size of the touch pad apart.
+static const float SWIPE_MAX_WIDTH_RATIO = 0.333f; // 1/3
+
+// The gesture movement speed factor relative to the size of the display.
+// Movement speed applies when the fingers are moving in the same direction.
+// Without acceleration, a full swipe of the touch pad diagonal in movement mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_MOVEMENT_SPEED_RATIO = 0.8f;
+
+// The gesture zoom speed factor relative to the size of the display.
+// Zoom speed applies when the fingers are mostly moving relative to each other
+// to execute a scale gesture or similar.
+// Without acceleration, a full swipe of the touch pad diagonal in zoom mode
+// will cover this portion of the display diagonal.
+static const float GESTURE_ZOOM_SPEED_RATIO = 0.3f;
+
// --- Static Functions ---
@@ -112,14 +140,8 @@
return (x + y) / 2;
}
-inline static float pythag(float x, float y) {
- return sqrtf(x * x + y * y);
-}
-
-inline static int32_t distanceSquared(int32_t x1, int32_t y1, int32_t x2, int32_t y2) {
- int32_t dx = x1 - x2;
- int32_t dy = y1 - y2;
- return dx * dx + dy * dy;
+inline static float distance(float x1, float y1, float x2, float y2) {
+ return hypotf(x1 - x2, y1 - y2);
}
inline static int32_t signExtendNybble(int32_t value) {
@@ -224,6 +246,33 @@
return edgeFlags;
}
+static void clampPositionUsingPointerBounds(
+ const sp<PointerControllerInterface>& pointerController, float* x, float* y) {
+ float minX, minY, maxX, maxY;
+ if (pointerController->getBounds(&minX, &minY, &maxX, &maxY)) {
+ if (*x < minX) {
+ *x = minX;
+ } else if (*x > maxX) {
+ *x = maxX;
+ }
+ if (*y < minY) {
+ *y = minY;
+ } else if (*y > maxY) {
+ *y = maxY;
+ }
+ }
+}
+
+static float calculateCommonVector(float a, float b) {
+ if (a > 0 && b > 0) {
+ return a < b ? a : b;
+ } else if (a < 0 && b < 0) {
+ return a > b ? a : b;
+ } else {
+ return 0;
+ }
+}
+
// --- InputReader ---
@@ -1553,10 +1602,32 @@
motionEventEdgeFlags = AMOTION_EVENT_EDGE_FLAG_NONE;
+ if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
+ vscroll = mAccumulator.relWheel;
+ } else {
+ vscroll = 0;
+ }
+ if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
+ hscroll = mAccumulator.relHWheel;
+ } else {
+ hscroll = 0;
+ }
+
if (mPointerController != NULL) {
- mPointerController->move(deltaX, deltaY);
- if (buttonsChanged) {
- mPointerController->setButtonState(mLocked.buttonState);
+ if (deltaX != 0 || deltaY != 0 || vscroll != 0 || hscroll != 0
+ || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::PRESENTATION_POINTER);
+
+ if (deltaX != 0 || deltaY != 0) {
+ mPointerController->move(deltaX, deltaY);
+ }
+
+ if (buttonsChanged) {
+ mPointerController->setButtonState(mLocked.buttonState);
+ }
+
+ mPointerController->unfade();
}
float x, y;
@@ -1574,20 +1645,6 @@
}
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
-
- if (mHaveVWheel && (fields & Accumulator::FIELD_REL_WHEEL)) {
- vscroll = mAccumulator.relWheel;
- } else {
- vscroll = 0;
- }
- if (mHaveHWheel && (fields & Accumulator::FIELD_REL_HWHEEL)) {
- hscroll = mAccumulator.relHWheel;
- } else {
- hscroll = 0;
- }
- if (hscroll != 0 || vscroll != 0) {
- mPointerController->unfade();
- }
} // release lock
// Moving an external trackball or mouse should wake the device.
@@ -1751,8 +1808,8 @@
mLocked.pointerGestureXZoomScale);
dump.appendFormat(INDENT4 "YZoomScale: %0.3f\n",
mLocked.pointerGestureYZoomScale);
- dump.appendFormat(INDENT4 "MaxSwipeWidthSquared: %d\n",
- mLocked.pointerGestureMaxSwipeWidthSquared);
+ dump.appendFormat(INDENT4 "MaxSwipeWidth: %f\n",
+ mLocked.pointerGestureMaxSwipeWidth);
}
} // release lock
}
@@ -1825,6 +1882,10 @@
mParameters.useJumpyTouchFilter = getPolicy()->filterJumpyTouchEvents();
mParameters.virtualKeyQuietTime = getPolicy()->getVirtualKeyQuietTime();
+ // TODO: Make this configurable.
+ //mParameters.gestureMode = Parameters::GESTURE_MODE_POINTER;
+ mParameters.gestureMode = Parameters::GESTURE_MODE_SPOTS;
+
if (getEventHub()->hasRelativeAxis(getDeviceId(), REL_X)
|| getEventHub()->hasRelativeAxis(getDeviceId(), REL_Y)) {
// The device is a cursor device with a touch pad attached.
@@ -1983,7 +2044,7 @@
mLocked.geometricScale = avg(mLocked.xScale, mLocked.yScale);
// Size of diagonal axis.
- float diagonalSize = pythag(width, height);
+ float diagonalSize = hypotf(width, height);
// TouchMajor and TouchMinor factors.
if (mCalibration.touchSizeCalibration != Calibration::TOUCH_SIZE_CALIBRATION_NONE) {
@@ -2178,30 +2239,39 @@
if (mParameters.deviceType == Parameters::DEVICE_TYPE_POINTER) {
int32_t rawWidth = mRawAxes.x.maxValue - mRawAxes.x.minValue + 1;
int32_t rawHeight = mRawAxes.y.maxValue - mRawAxes.y.minValue + 1;
+ float rawDiagonal = hypotf(rawWidth, rawHeight);
+ float displayDiagonal = hypotf(mLocked.associatedDisplayWidth,
+ mLocked.associatedDisplayHeight);
- // Scale movements such that one whole swipe of the touch pad covers a portion
- // of the display along whichever axis of the touch pad is longer.
+ // Scale movements such that one whole swipe of the touch pad covers a
+ // given area relative to the diagonal size of the display.
// Assume that the touch pad has a square aspect ratio such that movements in
// X and Y of the same number of raw units cover the same physical distance.
const float scaleFactor = 0.8f;
- mLocked.pointerGestureXMovementScale = rawWidth > rawHeight
- ? scaleFactor * float(mLocked.associatedDisplayWidth) / rawWidth
- : scaleFactor * float(mLocked.associatedDisplayHeight) / rawHeight;
+ mLocked.pointerGestureXMovementScale = GESTURE_MOVEMENT_SPEED_RATIO
+ * displayDiagonal / rawDiagonal;
mLocked.pointerGestureYMovementScale = mLocked.pointerGestureXMovementScale;
// Scale zooms to cover a smaller range of the display than movements do.
// This value determines the area around the pointer that is affected by freeform
// pointer gestures.
- mLocked.pointerGestureXZoomScale = mLocked.pointerGestureXMovementScale * 0.4f;
- mLocked.pointerGestureYZoomScale = mLocked.pointerGestureYMovementScale * 0.4f;
+ mLocked.pointerGestureXZoomScale = GESTURE_ZOOM_SPEED_RATIO
+ * displayDiagonal / rawDiagonal;
+ mLocked.pointerGestureYZoomScale = mLocked.pointerGestureXZoomScale;
- // Max width between pointers to detect a swipe gesture is 3/4 of the short
- // axis of the touch pad. Touches that are wider than this are translated
- // into freeform gestures.
- mLocked.pointerGestureMaxSwipeWidthSquared = min(rawWidth, rawHeight) * 3 / 4;
- mLocked.pointerGestureMaxSwipeWidthSquared *=
- mLocked.pointerGestureMaxSwipeWidthSquared;
+ // Max width between pointers to detect a swipe gesture is more than some fraction
+ // of the diagonal axis of the touch pad. Touches that are wider than this are
+ // translated into freeform gestures.
+ mLocked.pointerGestureMaxSwipeWidth = SWIPE_MAX_WIDTH_RATIO * rawDiagonal;
+
+ // Reset the current pointer gesture.
+ mPointerGesture.reset();
+
+ // Remove any current spots.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->clearSpots();
+ }
}
}
@@ -2628,6 +2698,11 @@
{ // acquire lock
AutoMutex _l(mLock);
initializeLocked();
+
+ if (mPointerController != NULL
+ && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerController->clearSpots();
+ }
} // release lock
InputMapper::reset();
@@ -2691,14 +2766,21 @@
}
}
- // Process touches and virtual keys.
- TouchResult touchResult = consumeOffScreenTouches(when, policyFlags);
- if (touchResult == DISPATCH_TOUCH) {
- suppressSwipeOntoVirtualKeys(when);
- if (mPointerController != NULL) {
- dispatchPointerGestures(when, policyFlags);
+ TouchResult touchResult;
+ if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount == 0
+ && mLastTouch.buttonState == mCurrentTouch.buttonState) {
+ // Drop spurious syncs.
+ touchResult = DROP_STROKE;
+ } else {
+ // Process touches and virtual keys.
+ touchResult = consumeOffScreenTouches(when, policyFlags);
+ if (touchResult == DISPATCH_TOUCH) {
+ suppressSwipeOntoVirtualKeys(when);
+ if (mPointerController != NULL) {
+ dispatchPointerGestures(when, policyFlags, false /*isTimeout*/);
+ }
+ dispatchTouches(when, policyFlags);
}
- dispatchTouches(when, policyFlags);
}
// Copy current touch to last touch in preparation for the next cycle.
@@ -2711,6 +2793,12 @@
}
}
+void TouchInputMapper::timeoutExpired(nsecs_t when) {
+ if (mPointerController != NULL) {
+ dispatchPointerGestures(when, 0 /*policyFlags*/, true /*isTimeout*/);
+ }
+}
+
TouchInputMapper::TouchResult TouchInputMapper::consumeOffScreenTouches(
nsecs_t when, uint32_t policyFlags) {
int32_t keyEventAction, keyEventFlags;
@@ -3070,7 +3158,7 @@
int32_t c2 = signExtendNybble(in.orientation & 0x0f);
if (c1 != 0 || c2 != 0) {
orientation = atan2f(c1, c2) * 0.5f;
- float scale = 1.0f + pythag(c1, c2) / 16.0f;
+ float scale = 1.0f + hypotf(c1, c2) / 16.0f;
touchMajor *= scale;
touchMinor /= scale;
toolMajor *= scale;
@@ -3154,23 +3242,43 @@
*outYPrecision = mLocked.orientedYPrecision;
}
-void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) {
+void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags,
+ bool isTimeout) {
+ // Switch pointer presentation.
+ mPointerController->setPresentation(
+ mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ ? PointerControllerInterface::PRESENTATION_SPOT
+ : PointerControllerInterface::PRESENTATION_POINTER);
+
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
- preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture);
+ bool sendEvents = preparePointerGestures(when,
+ &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
+ if (!sendEvents) {
+ return;
+ }
+
+ // Show the pointer if needed.
+ if (mPointerGesture.currentGestureMode != PointerGesture::NEUTRAL
+ && mPointerGesture.currentGestureMode != PointerGesture::QUIET) {
+ mPointerController->unfade();
+ }
// Send events!
uint32_t metaState = getContext()->getGlobalMetaState();
// Update last coordinates of pointers that have moved so that we observe the new
// pointer positions at the same time as other pointers that have just gone up.
- bool down = mPointerGesture.currentGestureMode == PointerGesture::CLICK_OR_DRAG
+ bool down = mPointerGesture.currentGestureMode == PointerGesture::TAP
+ || mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ || mPointerGesture.currentGestureMode == PointerGesture::PRESS
|| mPointerGesture.currentGestureMode == PointerGesture::SWIPE
|| mPointerGesture.currentGestureMode == PointerGesture::FREEFORM;
bool moveNeeded = false;
if (down && !cancelPreviousGesture && !finishPreviousGesture
- && mPointerGesture.lastGesturePointerCount != 0
- && mPointerGesture.currentGesturePointerCount != 0) {
+ && !mPointerGesture.lastGestureIdBits.isEmpty()
+ && !mPointerGesture.currentGestureIdBits.isEmpty()) {
BitSet32 movedGestureIdBits(mPointerGesture.currentGestureIdBits.value
& mPointerGesture.lastGestureIdBits.value);
moveNeeded = updateMovedPointerCoords(
@@ -3251,27 +3359,6 @@
}
}
- // Send down and up for a tap.
- if (mPointerGesture.currentGestureMode == PointerGesture::TAP) {
- const PointerCoords& coords = mPointerGesture.currentGestureCoords[0];
- int32_t edgeFlags = calculateEdgeFlagsUsingPointerBounds(mPointerController,
- coords.getAxisValue(AMOTION_EVENT_AXIS_X),
- coords.getAxisValue(AMOTION_EVENT_AXIS_Y));
- nsecs_t downTime = mPointerGesture.downTime = mPointerGesture.tapTime;
- mPointerGesture.resetTapTime();
-
- dispatchMotion(downTime, policyFlags, mPointerSource,
- AMOTION_EVENT_ACTION_DOWN, 0, metaState, edgeFlags,
- mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
- mPointerGesture.currentGestureIdBits, -1,
- 0, 0, downTime);
- dispatchMotion(when, policyFlags, mPointerSource,
- AMOTION_EVENT_ACTION_UP, 0, metaState, edgeFlags,
- mPointerGesture.currentGestureCoords, mPointerGesture.currentGestureIdToIndex,
- mPointerGesture.currentGestureIdBits, -1,
- 0, 0, downTime);
- }
-
// Send motion events for hover.
if (mPointerGesture.currentGestureMode == PointerGesture::HOVER) {
dispatchMotion(when, policyFlags, mPointerSource,
@@ -3284,11 +3371,8 @@
// Update state.
mPointerGesture.lastGestureMode = mPointerGesture.currentGestureMode;
if (!down) {
- mPointerGesture.lastGesturePointerCount = 0;
mPointerGesture.lastGestureIdBits.clear();
} else {
- uint32_t currentGesturePointerCount = mPointerGesture.currentGesturePointerCount;
- mPointerGesture.lastGesturePointerCount = currentGesturePointerCount;
mPointerGesture.lastGestureIdBits = mPointerGesture.currentGestureIdBits;
for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty(); ) {
uint32_t id = idBits.firstMarkedBit();
@@ -3301,13 +3385,49 @@
}
}
-void TouchInputMapper::preparePointerGestures(nsecs_t when,
- bool* outCancelPreviousGesture, bool* outFinishPreviousGesture) {
+bool TouchInputMapper::preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout) {
*outCancelPreviousGesture = false;
*outFinishPreviousGesture = false;
AutoMutex _l(mLock);
+ // Handle TAP timeout.
+ if (isTimeout) {
+#if DEBUG_GESTURES
+ LOGD("Gestures: Processing timeout");
+#endif
+
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) {
+ // The tap/drag timeout has not yet expired.
+ getContext()->requestTimeoutAtTime(mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL);
+ } else {
+ // The tap is finished.
+#if DEBUG_GESTURES
+ LOGD("Gestures: TAP finished");
+#endif
+ *outFinishPreviousGesture = true;
+
+ mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
+ mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ mPointerGesture.spotIdBits.clear();
+ moveSpotsLocked();
+ }
+ return true;
+ }
+ }
+
+ // We did not handle this timeout.
+ return false;
+ }
+
// Update the velocity tracker.
{
VelocityTracker::Position positions[MAX_POINTERS];
@@ -3328,77 +3448,51 @@
// Choose an arbitrary pointer that just went down, if there is one.
// Otherwise choose an arbitrary remaining pointer.
// This guarantees we always have an active touch id when there is at least one pointer.
- // We always switch to the newest pointer down because that's usually where the user's
- // attention is focused.
- int32_t activeTouchId;
- BitSet32 downTouchIdBits(mCurrentTouch.idBits.value & ~mLastTouch.idBits.value);
- if (!downTouchIdBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId = downTouchIdBits.firstMarkedBit();
- } else {
- activeTouchId = mPointerGesture.activeTouchId;
- if (activeTouchId < 0 || !mCurrentTouch.idBits.hasBit(activeTouchId)) {
- if (!mCurrentTouch.idBits.isEmpty()) {
- activeTouchId = mPointerGesture.activeTouchId =
- mCurrentTouch.idBits.firstMarkedBit();
- } else {
- activeTouchId = mPointerGesture.activeTouchId = -1;
- }
+ // We keep the same active touch id for as long as possible.
+ bool activeTouchChanged = false;
+ int32_t lastActiveTouchId = mPointerGesture.activeTouchId;
+ int32_t activeTouchId = lastActiveTouchId;
+ if (activeTouchId < 0) {
+ if (!mCurrentTouch.idBits.isEmpty()) {
+ activeTouchChanged = true;
+ activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+ mPointerGesture.firstTouchTime = when;
}
- }
-
- // Update the touch origin data to track where each finger originally went down.
- if (mCurrentTouch.pointerCount == 0 || mPointerGesture.touchOrigin.pointerCount == 0) {
- // Fast path when all fingers have gone up or down.
- mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
- } else {
- // Slow path when only some fingers have gone up or down.
- for (BitSet32 idBits(mPointerGesture.touchOrigin.idBits.value
- & ~mCurrentTouch.idBits.value); !idBits.isEmpty(); ) {
- uint32_t id = idBits.firstMarkedBit();
- idBits.clearBit(id);
- mPointerGesture.touchOrigin.idBits.clearBit(id);
- uint32_t index = mPointerGesture.touchOrigin.idToIndex[id];
- uint32_t count = --mPointerGesture.touchOrigin.pointerCount;
- while (index < count) {
- mPointerGesture.touchOrigin.pointers[index] =
- mPointerGesture.touchOrigin.pointers[index + 1];
- uint32_t movedId = mPointerGesture.touchOrigin.pointers[index].id;
- mPointerGesture.touchOrigin.idToIndex[movedId] = index;
- index += 1;
- }
- }
- for (BitSet32 idBits(mCurrentTouch.idBits.value
- & ~mPointerGesture.touchOrigin.idBits.value); !idBits.isEmpty(); ) {
- uint32_t id = idBits.firstMarkedBit();
- idBits.clearBit(id);
- mPointerGesture.touchOrigin.idBits.markBit(id);
- uint32_t index = mPointerGesture.touchOrigin.pointerCount++;
- mPointerGesture.touchOrigin.pointers[index] =
- mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
- mPointerGesture.touchOrigin.idToIndex[id] = index;
+ } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) {
+ activeTouchChanged = true;
+ if (!mCurrentTouch.idBits.isEmpty()) {
+ activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit();
+ } else {
+ activeTouchId = mPointerGesture.activeTouchId = -1;
}
}
// Determine whether we are in quiet time.
- bool isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
- if (!isQuietTime) {
- if ((mPointerGesture.lastGestureMode == PointerGesture::SWIPE
- || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
- && mCurrentTouch.pointerCount < 2) {
- // Enter quiet time when exiting swipe or freeform state.
- // This is to prevent accidentally entering the hover state and flinging the
- // pointer when finishing a swipe and there is still one pointer left onscreen.
- isQuietTime = true;
- } else if (mPointerGesture.lastGestureMode == PointerGesture::CLICK_OR_DRAG
- && mCurrentTouch.pointerCount >= 2
- && !isPointerDown(mCurrentTouch.buttonState)) {
- // Enter quiet time when releasing the button and there are still two or more
- // fingers down. This may indicate that one finger was used to press the button
- // but it has not gone up yet.
- isQuietTime = true;
- }
- if (isQuietTime) {
- mPointerGesture.quietTime = when;
+ bool isQuietTime = false;
+ if (activeTouchId < 0) {
+ mPointerGesture.resetQuietTime();
+ } else {
+ isQuietTime = when < mPointerGesture.quietTime + QUIET_INTERVAL;
+ if (!isQuietTime) {
+ if ((mPointerGesture.lastGestureMode == PointerGesture::PRESS
+ || mPointerGesture.lastGestureMode == PointerGesture::SWIPE
+ || mPointerGesture.lastGestureMode == PointerGesture::FREEFORM)
+ && mCurrentTouch.pointerCount < 2) {
+ // Enter quiet time when exiting swipe or freeform state.
+ // This is to prevent accidentally entering the hover state and flinging the
+ // pointer when finishing a swipe and there is still one pointer left onscreen.
+ isQuietTime = true;
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::BUTTON_CLICK_OR_DRAG
+ && mCurrentTouch.pointerCount >= 2
+ && !isPointerDown(mCurrentTouch.buttonState)) {
+ // Enter quiet time when releasing the button and there are still two or more
+ // fingers down. This may indicate that one finger was used to press the button
+ // but it has not gone up yet.
+ isQuietTime = true;
+ }
+ if (isQuietTime) {
+ mPointerGesture.quietTime = when;
+ }
}
}
@@ -3413,10 +3507,17 @@
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::QUIET;
- mPointerGesture.currentGesturePointerCount = 0;
mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ mPointerGesture.spotIdBits.clear();
+ moveSpotsLocked();
+ }
} else if (isPointerDown(mCurrentTouch.buttonState)) {
- // Case 2: Button is pressed. (DRAG)
+ // Case 2: Button is pressed. (BUTTON_CLICK_OR_DRAG)
// The pointer follows the active touch point.
// Emit DOWN, MOVE, UP events at the pointer location.
//
@@ -3430,11 +3531,11 @@
// finger to drag then the active pointer should switch to the finger that is
// being dragged.
#if DEBUG_GESTURES
- LOGD("Gestures: CLICK_OR_DRAG activeTouchId=%d, "
+ LOGD("Gestures: BUTTON_CLICK_OR_DRAG activeTouchId=%d, "
"currentTouchPointerCount=%d", activeTouchId, mCurrentTouch.pointerCount);
#endif
// Reset state when just starting.
- if (mPointerGesture.lastGestureMode != PointerGesture::CLICK_OR_DRAG) {
+ if (mPointerGesture.lastGestureMode != PointerGesture::BUTTON_CLICK_OR_DRAG) {
*outFinishPreviousGesture = true;
mPointerGesture.activeGestureId = 0;
}
@@ -3449,7 +3550,7 @@
uint32_t id = mCurrentTouch.pointers[i].id;
float vx, vy;
if (mPointerGesture.velocityTracker.getVelocity(id, &vx, &vy)) {
- float speed = pythag(vx, vy);
+ float speed = hypotf(vx, vy);
if (speed > bestSpeed) {
bestId = id;
bestSpeed = speed;
@@ -3458,8 +3559,9 @@
}
if (bestId >= 0 && bestId != activeTouchId) {
mPointerGesture.activeTouchId = activeTouchId = bestId;
+ activeTouchChanged = true;
#if DEBUG_GESTURES
- LOGD("Gestures: CLICK_OR_DRAG switched pointers, "
+ LOGD("Gestures: BUTTON_CLICK_OR_DRAG switched pointers, "
"bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed);
#endif
}
@@ -3474,6 +3576,10 @@
* mLocked.pointerGestureXMovementScale;
float deltaY = (currentPointer.y - lastPointer.y)
* mLocked.pointerGestureYMovementScale;
+
+ // Move the pointer using a relative motion.
+ // When using spots, the click will occur at the position of the anchor
+ // spot and all other spots will move there.
mPointerController->move(deltaX, deltaY);
}
}
@@ -3481,8 +3587,7 @@
float x, y;
mPointerController->getPosition(&x, &y);
- mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG;
- mPointerGesture.currentGesturePointerCount = 1;
+ mPointerGesture.currentGestureMode = PointerGesture::BUTTON_CLICK_OR_DRAG;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
@@ -3490,26 +3595,54 @@
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ if (activeTouchId >= 0) {
+ // Collapse all spots into one point at the pointer location.
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_DRAG;
+ mPointerGesture.spotIdBits.clear();
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+ uint32_t id = mCurrentTouch.pointers[i].id;
+ mPointerGesture.spotIdBits.markBit(id);
+ mPointerGesture.spotIdToIndex[id] = i;
+ mPointerGesture.spotCoords[i] = mPointerGesture.currentGestureCoords[0];
+ }
+ } else {
+ // No fingers. Generate a spot at the pointer location so the
+ // anchor appears to be pressed.
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_BUTTON_CLICK;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(0);
+ mPointerGesture.spotIdToIndex[0] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ }
+ moveSpotsLocked();
+ }
} else if (mCurrentTouch.pointerCount == 0) {
// Case 3. No fingers down and button is not pressed. (NEUTRAL)
*outFinishPreviousGesture = true;
- // Watch for taps coming out of HOVER or INDETERMINATE_MULTITOUCH mode.
+ // Watch for taps coming out of HOVER or TAP_DRAG mode.
bool tapped = false;
- if (mPointerGesture.lastGestureMode == PointerGesture::HOVER
- || mPointerGesture.lastGestureMode
- == PointerGesture::INDETERMINATE_MULTITOUCH) {
- if (when <= mPointerGesture.tapTime + TAP_INTERVAL) {
+ if ((mPointerGesture.lastGestureMode == PointerGesture::HOVER
+ || mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG)
+ && mLastTouch.pointerCount == 1) {
+ if (when <= mPointerGesture.tapDownTime + TAP_INTERVAL) {
float x, y;
mPointerController->getPosition(&x, &y);
- if (fabs(x - mPointerGesture.initialPointerX) <= TAP_SLOP
- && fabs(y - mPointerGesture.initialPointerY) <= TAP_SLOP) {
+ if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+ && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
#if DEBUG_GESTURES
LOGD("Gestures: TAP");
#endif
+
+ mPointerGesture.tapUpTime = when;
+ getContext()->requestTimeoutAtTime(when + TAP_DRAG_INTERVAL);
+
mPointerGesture.activeGestureId = 0;
mPointerGesture.currentGestureMode = PointerGesture::TAP;
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(
mPointerGesture.activeGestureId);
@@ -3517,44 +3650,86 @@
mPointerGesture.activeGestureId] = 0;
mPointerGesture.currentGestureCoords[0].clear();
mPointerGesture.currentGestureCoords[0].setAxisValue(
- AMOTION_EVENT_AXIS_X, mPointerGesture.initialPointerX);
+ AMOTION_EVENT_AXIS_X, mPointerGesture.tapX);
mPointerGesture.currentGestureCoords[0].setAxisValue(
- AMOTION_EVENT_AXIS_Y, mPointerGesture.initialPointerY);
+ AMOTION_EVENT_AXIS_Y, mPointerGesture.tapY);
mPointerGesture.currentGestureCoords[0].setAxisValue(
AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_TAP;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(lastActiveTouchId);
+ mPointerGesture.spotIdToIndex[lastActiveTouchId] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ moveSpotsLocked();
+ }
+
tapped = true;
} else {
#if DEBUG_GESTURES
LOGD("Gestures: Not a TAP, deltaX=%f, deltaY=%f",
- x - mPointerGesture.initialPointerX,
- y - mPointerGesture.initialPointerY);
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
#endif
}
} else {
#if DEBUG_GESTURES
- LOGD("Gestures: Not a TAP, delay=%lld",
- when - mPointerGesture.tapTime);
+ LOGD("Gestures: Not a TAP, %0.3fms since down",
+ (when - mPointerGesture.tapDownTime) * 0.000001f);
#endif
}
}
+
if (!tapped) {
#if DEBUG_GESTURES
LOGD("Gestures: NEUTRAL");
#endif
mPointerGesture.activeGestureId = -1;
mPointerGesture.currentGestureMode = PointerGesture::NEUTRAL;
- mPointerGesture.currentGesturePointerCount = 0;
mPointerGesture.currentGestureIdBits.clear();
+
+ mPointerController->setButtonState(0);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ mPointerGesture.spotIdBits.clear();
+ moveSpotsLocked();
+ }
}
} else if (mCurrentTouch.pointerCount == 1) {
- // Case 4. Exactly one finger down, button is not pressed. (HOVER)
+ // Case 4. Exactly one finger down, button is not pressed. (HOVER or TAP_DRAG)
// The pointer follows the active touch point.
- // Emit HOVER_MOVE events at the pointer location.
+ // When in HOVER, emit HOVER_MOVE events at the pointer location.
+ // When in TAP_DRAG, emit MOVE events at the pointer location.
LOG_ASSERT(activeTouchId >= 0);
+ mPointerGesture.currentGestureMode = PointerGesture::HOVER;
+ if (mPointerGesture.lastGestureMode == PointerGesture::TAP) {
+ if (when <= mPointerGesture.tapUpTime + TAP_DRAG_INTERVAL) {
+ float x, y;
+ mPointerController->getPosition(&x, &y);
+ if (fabs(x - mPointerGesture.tapX) <= TAP_SLOP
+ && fabs(y - mPointerGesture.tapY) <= TAP_SLOP) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ } else {
#if DEBUG_GESTURES
- LOGD("Gestures: HOVER");
+ LOGD("Gestures: Not a TAP_DRAG, deltaX=%f, deltaY=%f",
+ x - mPointerGesture.tapX,
+ y - mPointerGesture.tapY);
#endif
+ }
+ } else {
+#if DEBUG_GESTURES
+ LOGD("Gestures: Not a TAP_DRAG, %0.3fms time since up",
+ (when - mPointerGesture.tapUpTime) * 0.000001f);
+#endif
+ }
+ } else if (mPointerGesture.lastGestureMode == PointerGesture::TAP_DRAG) {
+ mPointerGesture.currentGestureMode = PointerGesture::TAP_DRAG;
+ }
if (mLastTouch.idBits.hasBit(activeTouchId)) {
const PointerData& currentPointer =
@@ -3565,180 +3740,305 @@
* mLocked.pointerGestureXMovementScale;
float deltaY = (currentPointer.y - lastPointer.y)
* mLocked.pointerGestureYMovementScale;
+
+ // Move the pointer using a relative motion.
+ // When using spots, the hover or drag will occur at the position of the anchor spot.
mPointerController->move(deltaX, deltaY);
}
- *outFinishPreviousGesture = true;
- mPointerGesture.activeGestureId = 0;
+ bool down;
+ if (mPointerGesture.currentGestureMode == PointerGesture::TAP_DRAG) {
+#if DEBUG_GESTURES
+ LOGD("Gestures: TAP_DRAG");
+#endif
+ down = true;
+ } else {
+#if DEBUG_GESTURES
+ LOGD("Gestures: HOVER");
+#endif
+ *outFinishPreviousGesture = true;
+ mPointerGesture.activeGestureId = 0;
+ down = false;
+ }
float x, y;
mPointerController->getPosition(&x, &y);
- mPointerGesture.currentGestureMode = PointerGesture::HOVER;
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
mPointerGesture.currentGestureCoords[0].clear();
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
+ down ? 1.0f : 0.0f);
+
+ mPointerController->setButtonState(down ? BUTTON_STATE_PRIMARY : 0);
if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
- mPointerGesture.tapTime = when;
- mPointerGesture.initialPointerX = x;
- mPointerGesture.initialPointerY = y;
+ mPointerGesture.resetTap();
+ mPointerGesture.tapDownTime = when;
+ mPointerGesture.tapX = x;
+ mPointerGesture.tapY = y;
+ }
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = down ? PointerControllerInterface::SPOT_GESTURE_DRAG
+ : PointerControllerInterface::SPOT_GESTURE_HOVER;
+ mPointerGesture.spotIdBits.clear();
+ mPointerGesture.spotIdBits.markBit(activeTouchId);
+ mPointerGesture.spotIdToIndex[activeTouchId] = 0;
+ mPointerGesture.spotCoords[0] = mPointerGesture.currentGestureCoords[0];
+ moveSpotsLocked();
}
} else {
- // Case 5. At least two fingers down, button is not pressed. (SWIPE or FREEFORM
- // or INDETERMINATE_MULTITOUCH)
- // Initially we watch and wait for something interesting to happen so as to
- // avoid making a spurious guess as to the nature of the gesture. For example,
- // the fingers may be in transition to some other state such as pressing or
- // releasing the button or we may be performing a two finger tap.
+ // Case 5. At least two fingers down, button is not pressed. (PRESS, SWIPE or FREEFORM)
+ // We need to provide feedback for each finger that goes down so we cannot wait
+ // for the fingers to move before deciding what to do.
//
- // Fix the centroid of the figure when the gesture actually starts.
- // We do not recalculate the centroid at any other time during the gesture because
- // it would affect the relationship of the touch points relative to the pointer location.
+ // The ambiguous case is deciding what to do when there are two fingers down but they
+ // have not moved enough to determine whether they are part of a drag or part of a
+ // freeform gesture, or just a press or long-press at the pointer location.
+ //
+ // When there are two fingers we start with the PRESS hypothesis and we generate a
+ // down at the pointer location.
+ //
+ // When the two fingers move enough or when additional fingers are added, we make
+ // a decision to transition into SWIPE or FREEFORM mode accordingly.
LOG_ASSERT(activeTouchId >= 0);
- uint32_t currentTouchPointerCount = mCurrentTouch.pointerCount;
- if (currentTouchPointerCount > MAX_POINTERS) {
- currentTouchPointerCount = MAX_POINTERS;
- }
-
- if (mPointerGesture.lastGestureMode != PointerGesture::INDETERMINATE_MULTITOUCH
+ bool needReference = false;
+ bool settled = when >= mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL;
+ if (mPointerGesture.lastGestureMode != PointerGesture::PRESS
&& mPointerGesture.lastGestureMode != PointerGesture::SWIPE
&& mPointerGesture.lastGestureMode != PointerGesture::FREEFORM) {
- mPointerGesture.currentGestureMode = PointerGesture::INDETERMINATE_MULTITOUCH;
-
*outFinishPreviousGesture = true;
- mPointerGesture.activeGestureId = -1;
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
- // Remember the initial pointer location.
- // Everything we do will be relative to this location.
- mPointerController->getPosition(&mPointerGesture.initialPointerX,
- &mPointerGesture.initialPointerY);
-
- // Track taps.
- if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) {
- mPointerGesture.tapTime = when;
+ if (settled && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS
+ && mLastTouch.idBits.hasBit(mPointerGesture.activeTouchId)) {
+ // The spot is already visible and has settled, use it as the reference point
+ // for the gesture. Other spots will be positioned relative to this one.
+#if DEBUG_GESTURES
+ LOGD("Gestures: Using active spot as reference for MULTITOUCH, "
+ "settle time expired %0.3fms ago",
+ (when - mPointerGesture.firstTouchTime - MULTITOUCH_SETTLE_INTERVAL)
+ * 0.000001f);
+#endif
+ const PointerData& d = mLastTouch.pointers[mLastTouch.idToIndex[
+ mPointerGesture.activeTouchId]];
+ mPointerGesture.referenceTouchX = d.x;
+ mPointerGesture.referenceTouchY = d.y;
+ const PointerCoords& c = mPointerGesture.spotCoords[mPointerGesture.spotIdToIndex[
+ mPointerGesture.activeTouchId]];
+ mPointerGesture.referenceGestureX = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ mPointerGesture.referenceGestureY = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+ } else {
+#if DEBUG_GESTURES
+ LOGD("Gestures: Using centroid as reference for MULTITOUCH, "
+ "settle time remaining %0.3fms",
+ (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+ * 0.000001f);
+#endif
+ needReference = true;
}
-
- // Reset the touch origin to be relative to exactly where the fingers are now
- // in case they have moved some distance away as part of a previous gesture.
- // We want to know how far the fingers have traveled since we started considering
- // a multitouch gesture.
- mPointerGesture.touchOrigin.copyFrom(mCurrentTouch);
+ } else if (!settled && mCurrentTouch.pointerCount > mLastTouch.pointerCount) {
+ // Additional pointers have gone down but not yet settled.
+ // Reset the gesture.
+#if DEBUG_GESTURES
+ LOGD("Gestures: Resetting gesture since additional pointers went down for MULTITOUCH, "
+ "settle time remaining %0.3fms",
+ (mPointerGesture.firstTouchTime + MULTITOUCH_SETTLE_INTERVAL - when)
+ * 0.000001f);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::PRESS;
+ mPointerGesture.activeGestureId = 0;
} else {
+ // Continue previous gesture.
mPointerGesture.currentGestureMode = mPointerGesture.lastGestureMode;
}
- if (mPointerGesture.currentGestureMode == PointerGesture::INDETERMINATE_MULTITOUCH) {
- // Wait for the pointers to start moving before doing anything.
- bool decideNow = true;
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
- const PointerData& current = mCurrentTouch.pointers[i];
- const PointerData& origin = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current.id]];
- float distance = pythag(
- (current.x - origin.x) * mLocked.pointerGestureXZoomScale,
- (current.y - origin.y) * mLocked.pointerGestureYZoomScale);
- if (distance < MULTITOUCH_MIN_TRAVEL) {
- decideNow = false;
- break;
- }
- }
+ if (needReference) {
+ // Use the centroid and pointer location as the reference points for the gesture.
+ mCurrentTouch.getCentroid(&mPointerGesture.referenceTouchX,
+ &mPointerGesture.referenceTouchY);
+ mPointerController->getPosition(&mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
- if (decideNow) {
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ float d;
+ if (mCurrentTouch.pointerCount > 2) {
+ // There are more than two pointers, switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, number of pointers %d > 2",
+ mCurrentTouch.pointerCount);
+#endif
+ *outCancelPreviousGesture = true;
mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
- if (currentTouchPointerCount == 2
- && distanceSquared(
- mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
- mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y)
- <= mLocked.pointerGestureMaxSwipeWidthSquared) {
- const PointerData& current1 = mCurrentTouch.pointers[0];
- const PointerData& current2 = mCurrentTouch.pointers[1];
- const PointerData& origin1 = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current1.id]];
- const PointerData& origin2 = mPointerGesture.touchOrigin.pointers[
- mPointerGesture.touchOrigin.idToIndex[current2.id]];
+ } else if (((d = distance(
+ mCurrentTouch.pointers[0].x, mCurrentTouch.pointers[0].y,
+ mCurrentTouch.pointers[1].x, mCurrentTouch.pointers[1].y))
+ > mLocked.pointerGestureMaxSwipeWidth)) {
+ // There are two pointers but they are too far apart, switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, distance %0.3f > %0.3f",
+ d, mLocked.pointerGestureMaxSwipeWidth);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
+ } else {
+ // There are two pointers. Wait for both pointers to start moving
+ // before deciding whether this is a SWIPE or FREEFORM gesture.
+ uint32_t id1 = mCurrentTouch.pointers[0].id;
+ uint32_t id2 = mCurrentTouch.pointers[1].id;
- float x1 = (current1.x - origin1.x) * mLocked.pointerGestureXZoomScale;
- float y1 = (current1.y - origin1.y) * mLocked.pointerGestureYZoomScale;
- float x2 = (current2.x - origin2.x) * mLocked.pointerGestureXZoomScale;
- float y2 = (current2.y - origin2.y) * mLocked.pointerGestureYZoomScale;
- float magnitude1 = pythag(x1, y1);
- float magnitude2 = pythag(x2, y2);
+ float vx1, vy1, vx2, vy2;
+ mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1);
+ mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2);
- // Calculate the dot product of the vectors.
+ float speed1 = hypotf(vx1, vy1);
+ float speed2 = hypotf(vx2, vy2);
+ if (speed1 >= MULTITOUCH_MIN_SPEED && speed2 >= MULTITOUCH_MIN_SPEED) {
+ // Calculate the dot product of the velocity vectors.
// When the vectors are oriented in approximately the same direction,
// the angle betweeen them is near zero and the cosine of the angle
// approches 1.0. Recall that dot(v1, v2) = cos(angle) * mag(v1) * mag(v2).
- // We know that the magnitude is at least MULTITOUCH_MIN_TRAVEL because
- // we checked it above.
- float dot = x1 * x2 + y1 * y2;
- float cosine = dot / (magnitude1 * magnitude2); // denominator always > 0
- if (cosine > SWIPE_TRANSITION_ANGLE_COSINE) {
+ float dot = vx1 * vx2 + vy1 * vy2;
+ float cosine = dot / (speed1 * speed2); // denominator always > 0
+ if (cosine >= SWIPE_TRANSITION_ANGLE_COSINE) {
+ // Pointers are moving in the same direction. Switch to SWIPE.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to SWIPE, "
+ "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+ "cosine %0.3f >= %0.3f",
+ speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+ cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
mPointerGesture.currentGestureMode = PointerGesture::SWIPE;
+ } else {
+ // Pointers are moving in different directions. Switch to FREEFORM.
+#if DEBUG_GESTURES
+ LOGD("Gestures: PRESS transitioned to FREEFORM, "
+ "speed1 %0.3f >= %0.3f, speed2 %0.3f >= %0.3f, "
+ "cosine %0.3f < %0.3f",
+ speed1, MULTITOUCH_MIN_SPEED, speed2, MULTITOUCH_MIN_SPEED,
+ cosine, SWIPE_TRANSITION_ANGLE_COSINE);
+#endif
+ *outCancelPreviousGesture = true;
+ mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
}
}
-
- // Remember the initial centroid for the duration of the gesture.
- mPointerGesture.initialCentroidX = 0;
- mPointerGesture.initialCentroidY = 0;
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
- const PointerData& touch = mCurrentTouch.pointers[i];
- mPointerGesture.initialCentroidX += touch.x;
- mPointerGesture.initialCentroidY += touch.y;
- }
- mPointerGesture.initialCentroidX /= int32_t(currentTouchPointerCount);
- mPointerGesture.initialCentroidY /= int32_t(currentTouchPointerCount);
-
- mPointerGesture.activeGestureId = 0;
}
} else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
- // Switch to FREEFORM if additional pointers go down.
- if (currentTouchPointerCount > 2) {
+ // Switch from SWIPE to FREEFORM if additional pointers go down.
+ // Cancel previous gesture.
+ if (mCurrentTouch.pointerCount > 2) {
+#if DEBUG_GESTURES
+ LOGD("Gestures: SWIPE transitioned to FREEFORM, number of pointers %d > 2",
+ mCurrentTouch.pointerCount);
+#endif
*outCancelPreviousGesture = true;
mPointerGesture.currentGestureMode = PointerGesture::FREEFORM;
}
}
- if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
- // SWIPE mode.
+ // Move the reference points based on the overall group motion of the fingers.
+ // The objective is to calculate a vector delta that is common to the movement
+ // of all fingers.
+ BitSet32 commonIdBits(mLastTouch.idBits.value & mCurrentTouch.idBits.value);
+ if (!commonIdBits.isEmpty()) {
+ float commonDeltaX = 0, commonDeltaY = 0;
+ for (BitSet32 idBits(commonIdBits); !idBits.isEmpty(); ) {
+ bool first = (idBits == commonIdBits);
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+
+ const PointerData& cpd = mCurrentTouch.pointers[mCurrentTouch.idToIndex[id]];
+ const PointerData& lpd = mLastTouch.pointers[mLastTouch.idToIndex[id]];
+ float deltaX = cpd.x - lpd.x;
+ float deltaY = cpd.y - lpd.y;
+
+ if (first) {
+ commonDeltaX = deltaX;
+ commonDeltaY = deltaY;
+ } else {
+ commonDeltaX = calculateCommonVector(commonDeltaX, deltaX);
+ commonDeltaY = calculateCommonVector(commonDeltaY, deltaY);
+ }
+ }
+
+ mPointerGesture.referenceTouchX += commonDeltaX;
+ mPointerGesture.referenceTouchY += commonDeltaY;
+ mPointerGesture.referenceGestureX +=
+ commonDeltaX * mLocked.pointerGestureXMovementScale;
+ mPointerGesture.referenceGestureY +=
+ commonDeltaY * mLocked.pointerGestureYMovementScale;
+ clampPositionUsingPointerBounds(mPointerController,
+ &mPointerGesture.referenceGestureX,
+ &mPointerGesture.referenceGestureY);
+ }
+
+ // Report gestures.
+ if (mPointerGesture.currentGestureMode == PointerGesture::PRESS) {
+ // PRESS mode.
#if DEBUG_GESTURES
- LOGD("Gestures: SWIPE activeTouchId=%d,"
+ LOGD("Gestures: PRESS activeTouchId=%d,"
"activeGestureId=%d, currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
#endif
LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
- float x = (mCurrentTouch.pointers[0].x + mCurrentTouch.pointers[1].x
- - mPointerGesture.initialCentroidX * 2) * 0.5f
- * mLocked.pointerGestureXMovementScale + mPointerGesture.initialPointerX;
- float y = (mCurrentTouch.pointers[0].y + mCurrentTouch.pointers[1].y
- - mPointerGesture.initialCentroidY * 2) * 0.5f
- * mLocked.pointerGestureYMovementScale + mPointerGesture.initialPointerY;
-
- mPointerGesture.currentGesturePointerCount = 1;
mPointerGesture.currentGestureIdBits.clear();
mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
mPointerGesture.currentGestureCoords[0].clear();
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
- mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(BUTTON_STATE_PRIMARY);
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_PRESS;
+ }
+ } else if (mPointerGesture.currentGestureMode == PointerGesture::SWIPE) {
+ // SWIPE mode.
+#if DEBUG_GESTURES
+ LOGD("Gestures: SWIPE activeTouchId=%d,"
+ "activeGestureId=%d, currentTouchPointerCount=%d",
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
+#endif
+ LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
+
+ mPointerGesture.currentGestureIdBits.clear();
+ mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId);
+ mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0;
+ mPointerGesture.currentGestureCoords[0].clear();
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+ mPointerGesture.referenceGestureX);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ mPointerGesture.referenceGestureY);
+ mPointerGesture.currentGestureCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+
+ mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_SWIPE;
+ }
} else if (mPointerGesture.currentGestureMode == PointerGesture::FREEFORM) {
// FREEFORM mode.
#if DEBUG_GESTURES
LOGD("Gestures: FREEFORM activeTouchId=%d,"
"activeGestureId=%d, currentTouchPointerCount=%d",
- activeTouchId, mPointerGesture.activeGestureId, currentTouchPointerCount);
+ activeTouchId, mPointerGesture.activeGestureId, mCurrentTouch.pointerCount);
#endif
LOG_ASSERT(mPointerGesture.activeGestureId >= 0);
- mPointerGesture.currentGesturePointerCount = currentTouchPointerCount;
mPointerGesture.currentGestureIdBits.clear();
BitSet32 mappedTouchIdBits;
@@ -3782,7 +4082,7 @@
mPointerGesture.activeGestureId);
#endif
- for (uint32_t i = 0; i < currentTouchPointerCount; i++) {
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
uint32_t touchId = mCurrentTouch.pointers[i].id;
uint32_t gestureId;
if (!mappedTouchIdBits.hasBit(touchId)) {
@@ -3805,10 +4105,10 @@
mPointerGesture.currentGestureIdBits.markBit(gestureId);
mPointerGesture.currentGestureIdToIndex[gestureId] = i;
- float x = (mCurrentTouch.pointers[i].x - mPointerGesture.initialCentroidX)
- * mLocked.pointerGestureXZoomScale + mPointerGesture.initialPointerX;
- float y = (mCurrentTouch.pointers[i].y - mPointerGesture.initialCentroidY)
- * mLocked.pointerGestureYZoomScale + mPointerGesture.initialPointerY;
+ float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+ * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+ float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+ * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
mPointerGesture.currentGestureCoords[i].clear();
mPointerGesture.currentGestureCoords[i].setAxisValue(
@@ -3827,30 +4127,45 @@
"activeGestureId=%d", mPointerGesture.activeGestureId);
#endif
}
- } else {
- // INDETERMINATE_MULTITOUCH mode.
- // Do nothing.
-#if DEBUG_GESTURES
- LOGD("Gestures: INDETERMINATE_MULTITOUCH");
-#endif
- }
- }
- // Unfade the pointer if the user is doing anything with the touch pad.
- mPointerController->setButtonState(mCurrentTouch.buttonState);
- if (mCurrentTouch.buttonState || mCurrentTouch.pointerCount != 0) {
- mPointerController->unfade();
+ mPointerController->setButtonState(0); // touch is not actually following the pointer
+
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM;
+ }
+ }
+
+ // Update spot locations for PRESS, SWIPE and FREEFORM.
+ // We use the same calculation as we do to calculate the gesture pointers
+ // for FREEFORM so that the spots smoothly track gestures.
+ if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) {
+ mPointerGesture.spotIdBits.clear();
+ for (uint32_t i = 0; i < mCurrentTouch.pointerCount; i++) {
+ uint32_t id = mCurrentTouch.pointers[i].id;
+ mPointerGesture.spotIdBits.markBit(id);
+ mPointerGesture.spotIdToIndex[id] = i;
+
+ float x = (mCurrentTouch.pointers[i].x - mPointerGesture.referenceTouchX)
+ * mLocked.pointerGestureXZoomScale + mPointerGesture.referenceGestureX;
+ float y = (mCurrentTouch.pointers[i].y - mPointerGesture.referenceTouchY)
+ * mLocked.pointerGestureYZoomScale + mPointerGesture.referenceGestureY;
+
+ mPointerGesture.spotCoords[i].clear();
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ mPointerGesture.spotCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+ }
+ moveSpotsLocked();
+ }
}
#if DEBUG_GESTURES
LOGD("Gestures: finishPreviousGesture=%s, cancelPreviousGesture=%s, "
- "currentGestureMode=%d, currentGesturePointerCount=%d, currentGestureIdBits=0x%08x, "
- "lastGestureMode=%d, lastGesturePointerCount=%d, lastGestureIdBits=0x%08x",
+ "currentGestureMode=%d, currentGestureIdBits=0x%08x, "
+ "lastGestureMode=%d, lastGestureIdBits=0x%08x",
toString(*outFinishPreviousGesture), toString(*outCancelPreviousGesture),
- mPointerGesture.currentGestureMode, mPointerGesture.currentGesturePointerCount,
- mPointerGesture.currentGestureIdBits.value,
- mPointerGesture.lastGestureMode, mPointerGesture.lastGesturePointerCount,
- mPointerGesture.lastGestureIdBits.value);
+ mPointerGesture.currentGestureMode, mPointerGesture.currentGestureIdBits.value,
+ mPointerGesture.lastGestureMode, mPointerGesture.lastGestureIdBits.value);
for (BitSet32 idBits = mPointerGesture.currentGestureIdBits; !idBits.isEmpty(); ) {
uint32_t id = idBits.firstMarkedBit();
idBits.clearBit(id);
@@ -3872,6 +4187,12 @@
coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
}
#endif
+ return true;
+}
+
+void TouchInputMapper::moveSpotsLocked() {
+ mPointerController->setSpots(mPointerGesture.spotGesture,
+ mPointerGesture.spotCoords, mPointerGesture.spotIdToIndex, mPointerGesture.spotIdBits);
}
void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags, uint32_t source,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index 9ed1391..0485617 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,7 +20,6 @@
#include "EventHub.h"
#include "InputDispatcher.h"
#include "PointerController.h"
-#include "SpotController.h"
#include <ui/Input.h>
#include <ui/DisplayInfo.h>
@@ -90,9 +89,6 @@
/* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
-
- /* Gets a spot controller associated with the specified touch pad device. */
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
};
@@ -574,6 +570,7 @@
const int32_t* keyCodes, uint8_t* outFlags);
virtual void fadePointer();
+ virtual void timeoutExpired(nsecs_t when);
protected:
Mutex mLock;
@@ -648,6 +645,20 @@
idBits.clear();
buttonState = 0;
}
+
+ void getCentroid(float* outX, float* outY) {
+ float x = 0, y = 0;
+ if (pointerCount != 0) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ x += pointers[i].x;
+ y += pointers[i].y;
+ }
+ x /= pointerCount;
+ y /= pointerCount;
+ }
+ *outX = x;
+ *outY = y;
+ }
};
// Input sources supported by the device.
@@ -670,6 +681,12 @@
bool useJumpyTouchFilter;
bool useAveragingTouchFilter;
nsecs_t virtualKeyQuietTime;
+
+ enum GestureMode {
+ GESTURE_MODE_POINTER,
+ GESTURE_MODE_SPOTS,
+ };
+ GestureMode gestureMode;
} mParameters;
// Immutable calibration parameters in parsed form.
@@ -841,8 +858,8 @@
float pointerGestureXZoomScale;
float pointerGestureYZoomScale;
- // The maximum swipe width squared.
- int32_t pointerGestureMaxSwipeWidthSquared;
+ // The maximum swipe width.
+ float pointerGestureMaxSwipeWidth;
} mLocked;
virtual void configureParameters();
@@ -919,38 +936,47 @@
// Emits DOWN and UP events at the pointer location.
TAP,
+ // Exactly one finger dragging following a tap.
+ // Pointer follows the active finger.
+ // Emits DOWN, MOVE and UP events at the pointer location.
+ TAP_DRAG,
+
// Button is pressed.
// Pointer follows the active finger if there is one. Other fingers are ignored.
// Emits DOWN, MOVE and UP events at the pointer location.
- CLICK_OR_DRAG,
+ BUTTON_CLICK_OR_DRAG,
// Exactly one finger, button is not pressed.
// Pointer follows the active finger.
// Emits HOVER_MOVE events at the pointer location.
HOVER,
- // More than two fingers involved but they haven't moved enough for us
- // to figure out what is intended.
- INDETERMINATE_MULTITOUCH,
+ // Exactly two fingers but neither have moved enough to clearly indicate
+ // whether a swipe or freeform gesture was intended. We consider the
+ // pointer to be pressed so this enables clicking or long-pressing on buttons.
+ // Pointer does not move.
+ // Emits DOWN, MOVE and UP events with a single stationary pointer coordinate.
+ PRESS,
// Exactly two fingers moving in the same direction, button is not pressed.
// Pointer does not move.
// Emits DOWN, MOVE and UP events with a single pointer coordinate that
// follows the midpoint between both fingers.
- // The centroid is fixed when entering this state.
SWIPE,
// Two or more fingers moving in arbitrary directions, button is not pressed.
// Pointer does not move.
// Emits DOWN, POINTER_DOWN, MOVE, POINTER_UP and UP events that follow
// each finger individually relative to the initial centroid of the finger.
- // The centroid is fixed when entering this state.
FREEFORM,
// Waiting for quiet time to end before starting the next gesture.
QUIET,
};
+ // Time the first finger went down.
+ nsecs_t firstTouchTime;
+
// The active pointer id from the raw touch data.
int32_t activeTouchId; // -1 if none
@@ -959,67 +985,67 @@
// Pointer coords and ids for the current and previous pointer gesture.
Mode currentGestureMode;
- uint32_t currentGesturePointerCount;
BitSet32 currentGestureIdBits;
uint32_t currentGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords currentGestureCoords[MAX_POINTERS];
Mode lastGestureMode;
- uint32_t lastGesturePointerCount;
BitSet32 lastGestureIdBits;
uint32_t lastGestureIdToIndex[MAX_POINTER_ID + 1];
PointerCoords lastGestureCoords[MAX_POINTERS];
- // Tracks for all pointers originally went down.
- TouchData touchOrigin;
-
- // Describes how touch ids are mapped to gesture ids for freeform gestures.
- uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
-
- // Initial centroid of the movement.
- // Used to calculate how far the touch pointers have moved since the gesture started.
- int32_t initialCentroidX;
- int32_t initialCentroidY;
-
- // Initial pointer location.
- // Used to track where the pointer was when the gesture started.
- float initialPointerX;
- float initialPointerY;
+ // Pointer coords and ids for the current spots.
+ PointerControllerInterface::SpotGesture spotGesture;
+ BitSet32 spotIdBits; // same set of ids as touch ids
+ uint32_t spotIdToIndex[MAX_POINTER_ID + 1];
+ PointerCoords spotCoords[MAX_POINTERS];
// Time the pointer gesture last went down.
nsecs_t downTime;
- // Time we started waiting for a tap gesture.
- nsecs_t tapTime;
+ // Time when the pointer went down for a TAP.
+ nsecs_t tapDownTime;
+
+ // Time when the pointer went up for a TAP.
+ nsecs_t tapUpTime;
+
+ // Location of initial tap.
+ float tapX, tapY;
// Time we started waiting for quiescence.
nsecs_t quietTime;
+ // Reference points for multitouch gestures.
+ float referenceTouchX; // reference touch X/Y coordinates in surface units
+ float referenceTouchY;
+ float referenceGestureX; // reference gesture X/Y coordinates in pixels
+ float referenceGestureY;
+
+ // Describes how touch ids are mapped to gesture ids for freeform gestures.
+ uint32_t freeformTouchToGestureIdMap[MAX_POINTER_ID + 1];
+
// A velocity tracker for determining whether to switch active pointers during drags.
VelocityTracker velocityTracker;
void reset() {
+ firstTouchTime = LLONG_MIN;
activeTouchId = -1;
activeGestureId = -1;
currentGestureMode = NEUTRAL;
- currentGesturePointerCount = 0;
currentGestureIdBits.clear();
lastGestureMode = NEUTRAL;
- lastGesturePointerCount = 0;
lastGestureIdBits.clear();
- touchOrigin.clear();
- initialCentroidX = 0;
- initialCentroidY = 0;
- initialPointerX = 0;
- initialPointerY = 0;
+ spotGesture = PointerControllerInterface::SPOT_GESTURE_NEUTRAL;
+ spotIdBits.clear();
downTime = 0;
velocityTracker.clear();
- resetTapTime();
+ resetTap();
resetQuietTime();
}
- void resetTapTime() {
- tapTime = LLONG_MIN;
+ void resetTap() {
+ tapDownTime = LLONG_MIN;
+ tapUpTime = LLONG_MIN;
}
void resetQuietTime() {
@@ -1032,9 +1058,10 @@
TouchResult consumeOffScreenTouches(nsecs_t when, uint32_t policyFlags);
void dispatchTouches(nsecs_t when, uint32_t policyFlags);
void prepareTouches(int32_t* outEdgeFlags, float* outXPrecision, float* outYPrecision);
- void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags);
- void preparePointerGestures(nsecs_t when,
- bool* outCancelPreviousGesture, bool* outFinishPreviousGesture);
+ void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags, bool isTimeout);
+ bool preparePointerGestures(nsecs_t when,
+ bool* outCancelPreviousGesture, bool* outFinishPreviousGesture, bool isTimeout);
+ void moveSpotsLocked();
// Dispatches a motion event.
// If the changedId is >= 0 and the action is POINTER_DOWN or POINTER_UP, the
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index 15effb7..ffef720 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -36,40 +36,49 @@
// --- PointerController ---
// Time to wait before starting the fade when the pointer is inactive.
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
-static const nsecs_t INACTIVITY_FADE_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
+static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
+
+// Time to wait between animation frames.
+static const nsecs_t ANIMATION_FRAME_INTERVAL = 1000000000LL / 60;
+
+// Time to spend fading out the spot completely.
+static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
// Time to spend fading out the pointer completely.
-static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms
-
-// Time to wait between frames.
-static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60;
-
-// Amount to subtract from alpha per frame.
-static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
+static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
-PointerController::PointerController(const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) :
- mLooper(looper), mSpriteController(spriteController) {
+// --- PointerController ---
+
+PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
+ mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
mHandler = new WeakMessageHandler(this);
AutoMutex _l(mLock);
+ mLocked.animationPending = false;
+
mLocked.displayWidth = -1;
mLocked.displayHeight = -1;
mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
+ mLocked.presentation = PRESENTATION_POINTER;
+ mLocked.presentationChanged = false;
+
+ mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
+
+ mLocked.pointerIsFading = true; // keep the pointer initially faded
mLocked.pointerX = 0;
mLocked.pointerY = 0;
+ mLocked.pointerAlpha = 0.0f;
+ mLocked.pointerSprite = mSpriteController->createSprite();
+ mLocked.pointerIconChanged = false;
+
mLocked.buttonState = 0;
- mLocked.fadeAlpha = 1;
- mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
-
- mLocked.visible = false;
-
- mLocked.sprite = mSpriteController->createSprite();
+ loadResources();
}
PointerController::~PointerController() {
@@ -77,7 +86,13 @@
AutoMutex _l(mLock);
- mLocked.sprite.clear();
+ mLocked.pointerSprite.clear();
+
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ delete mLocked.spots.itemAt(i);
+ }
+ mLocked.spots.clear();
+ mLocked.recycledSprites.clear();
}
bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -130,8 +145,6 @@
if (mLocked.buttonState != buttonState) {
mLocked.buttonState = buttonState;
- unfadeBeforeUpdateLocked();
- updateLocked();
}
}
@@ -167,8 +180,7 @@
} else {
mLocked.pointerY = y;
}
- unfadeBeforeUpdateLocked();
- updateLocked();
+ updatePointerLocked();
}
}
@@ -182,32 +194,105 @@
void PointerController::fade() {
AutoMutex _l(mLock);
- startFadeLocked();
+ sendImmediateInactivityTimeoutLocked();
}
void PointerController::unfade() {
AutoMutex _l(mLock);
- if (unfadeBeforeUpdateLocked()) {
- updateLocked();
+ // Always reset the inactivity timer.
+ resetInactivityTimeoutLocked();
+
+ // Unfade immediately if needed.
+ if (mLocked.pointerIsFading) {
+ mLocked.pointerIsFading = false;
+ mLocked.pointerAlpha = 1.0f;
+ updatePointerLocked();
}
}
-void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay) {
+void PointerController::setPresentation(Presentation presentation) {
AutoMutex _l(mLock);
- if (mLocked.inactivityFadeDelay != inactivityFadeDelay) {
- mLocked.inactivityFadeDelay = inactivityFadeDelay;
- startInactivityFadeDelayLocked();
+ if (mLocked.presentation != presentation) {
+ mLocked.presentation = presentation;
+ mLocked.presentationChanged = true;
+
+ if (presentation != PRESENTATION_SPOT) {
+ fadeOutAndReleaseAllSpotsLocked();
+ }
+
+ updatePointerLocked();
}
}
-void PointerController::updateLocked() {
- mLocked.sprite->openTransaction();
- mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
- mLocked.sprite->setAlpha(mLocked.fadeAlpha);
- mLocked.sprite->setVisible(mLocked.visible);
- mLocked.sprite->closeTransaction();
+void PointerController::setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+#if DEBUG_POINTER_UPDATES
+ LOGD("setSpots: spotGesture=%d", spotGesture);
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ LOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
+ c.getAxisValue(AMOTION_EVENT_AXIS_X),
+ c.getAxisValue(AMOTION_EVENT_AXIS_Y),
+ c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ }
+#endif
+
+ AutoMutex _l(mLock);
+
+ mSpriteController->openTransaction();
+
+ // Add or move spots for fingers that are down.
+ for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
+ uint32_t id = idBits.firstMarkedBit();
+ idBits.clearBit(id);
+
+ const PointerCoords& c = spotCoords[spotIdToIndex[id]];
+ const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
+ ? mResources.spotTouch : mResources.spotHover;
+ float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
+ float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
+
+ Spot* spot = getSpotLocked(id);
+ if (!spot) {
+ spot = createAndAddSpotLocked(id);
+ }
+
+ spot->updateSprite(&icon, x, y);
+ }
+
+ // Remove spots for fingers that went up.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id != Spot::INVALID_ID
+ && !spotIdBits.hasBit(spot->id)) {
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+void PointerController::clearSpots() {
+#if DEBUG_POINTER_UPDATES
+ LOGD("clearSpots");
+#endif
+
+ AutoMutex _l(mLock);
+
+ fadeOutAndReleaseAllSpotsLocked();
+}
+
+void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
+ AutoMutex _l(mLock);
+
+ if (mLocked.inactivityTimeout != inactivityTimeout) {
+ mLocked.inactivityTimeout = inactivityTimeout;
+ resetInactivityTimeoutLocked();
+ }
}
void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -226,7 +311,8 @@
mLocked.pointerY = 0;
}
- updateLocked();
+ fadeOutAndReleaseAllSpotsLocked();
+ updatePointerLocked();
}
}
@@ -283,74 +369,217 @@
mLocked.pointerY = y - 0.5f;
mLocked.displayOrientation = orientation;
- updateLocked();
+ updatePointerLocked();
}
}
-void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
+void PointerController::setPointerIcon(const SpriteIcon& icon) {
AutoMutex _l(mLock);
- mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
+ mLocked.pointerIcon = icon.copy();
+ mLocked.pointerIconChanged = true;
+
+ updatePointerLocked();
}
void PointerController::handleMessage(const Message& message) {
switch (message.what) {
- case MSG_FADE_STEP: {
- AutoMutex _l(mLock);
- fadeStepLocked();
+ case MSG_ANIMATE:
+ doAnimate();
+ break;
+ case MSG_INACTIVITY_TIMEOUT:
+ doInactivityTimeout();
break;
}
- }
}
-bool PointerController::unfadeBeforeUpdateLocked() {
- sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
+void PointerController::doAnimate() {
+ AutoMutex _l(mLock);
- if (isFadingLocked()) {
- mLocked.visible = true;
- mLocked.fadeAlpha = 1;
- return true; // update required to effect the unfade
- }
- return false; // update not required
-}
+ bool keepAnimating = false;
+ mLocked.animationPending = false;
+ nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime;
-void PointerController::startFadeLocked() {
- if (!isFadingLocked()) {
- sendFadeStepMessageDelayedLocked(0);
- }
-}
-
-void PointerController::startInactivityFadeDelayLocked() {
- if (!isFadingLocked()) {
- sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
- }
-}
-
-void PointerController::fadeStepLocked() {
- if (mLocked.visible) {
- mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
- if (mLocked.fadeAlpha < 0) {
- mLocked.fadeAlpha = 0;
- mLocked.visible = false;
+ // Animate pointer fade.
+ if (mLocked.pointerIsFading) {
+ mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
+ if (mLocked.pointerAlpha <= 0) {
+ mLocked.pointerAlpha = 0;
} else {
- sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
+ keepAnimating = true;
}
- updateLocked();
+ updatePointerLocked();
+ }
+
+ // Animate spots that are fading out and being removed.
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
+ if (spot->alpha <= 0) {
+ mLocked.spots.removeAt(i--);
+ releaseSpotLocked(spot);
+ } else {
+ spot->sprite->setAlpha(spot->alpha);
+ keepAnimating = true;
+ }
+ }
+ }
+
+ if (keepAnimating) {
+ startAnimationLocked();
}
}
-bool PointerController::isFadingLocked() {
- return !mLocked.visible || mLocked.fadeAlpha != 1;
+void PointerController::doInactivityTimeout() {
+ AutoMutex _l(mLock);
+
+ if (!mLocked.pointerIsFading) {
+ mLocked.pointerIsFading = true;
+ startAnimationLocked();
+ }
}
-nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
- return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT
- ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL;
+void PointerController::startAnimationLocked() {
+ if (!mLocked.animationPending) {
+ mLocked.animationPending = true;
+ mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mLooper->sendMessageDelayed(ANIMATION_FRAME_INTERVAL, mHandler, Message(MSG_ANIMATE));
+ }
}
-void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) {
- mLooper->removeMessages(mHandler, MSG_FADE_STEP);
- mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP));
+void PointerController::resetInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+
+ nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
+ ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
+ mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::sendImmediateInactivityTimeoutLocked() {
+ mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
+ mLooper->sendMessage(mHandler, MSG_INACTIVITY_TIMEOUT);
+}
+
+void PointerController::updatePointerLocked() {
+ mSpriteController->openTransaction();
+
+ mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
+ mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+
+ if (mLocked.pointerAlpha > 0) {
+ mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
+ mLocked.pointerSprite->setVisible(true);
+ } else {
+ mLocked.pointerSprite->setVisible(false);
+ }
+
+ if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
+ mLocked.pointerSprite->setIcon(mLocked.presentation == PRESENTATION_POINTER
+ ? mLocked.pointerIcon : mResources.spotAnchor);
+ mLocked.pointerIconChanged = false;
+ mLocked.presentationChanged = false;
+ }
+
+ mSpriteController->closeTransaction();
+}
+
+PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == id) {
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
+ // Remove spots until we have fewer than MAX_SPOTS remaining.
+ while (mLocked.spots.size() >= MAX_SPOTS) {
+ Spot* spot = removeFirstFadingSpotLocked();
+ if (!spot) {
+ spot = mLocked.spots.itemAt(0);
+ mLocked.spots.removeAt(0);
+ }
+ releaseSpotLocked(spot);
+ }
+
+ // Obtain a sprite from the recycled pool.
+ sp<Sprite> sprite;
+ if (! mLocked.recycledSprites.isEmpty()) {
+ sprite = mLocked.recycledSprites.top();
+ mLocked.recycledSprites.pop();
+ } else {
+ sprite = mSpriteController->createSprite();
+ }
+
+ // Return the new spot.
+ Spot* spot = new Spot(id, sprite);
+ mLocked.spots.push(spot);
+ return spot;
+}
+
+PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ if (spot->id == Spot::INVALID_ID) {
+ mLocked.spots.removeAt(i);
+ return spot;
+ }
+ }
+ return NULL;
+}
+
+void PointerController::releaseSpotLocked(Spot* spot) {
+ spot->sprite->clearIcon();
+
+ if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
+ mLocked.recycledSprites.push(spot->sprite);
+ }
+
+ delete spot;
+}
+
+void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
+ if (spot->id != Spot::INVALID_ID) {
+ spot->id = Spot::INVALID_ID;
+ startAnimationLocked();
+ }
+}
+
+void PointerController::fadeOutAndReleaseAllSpotsLocked() {
+ for (size_t i = 0; i < mLocked.spots.size(); i++) {
+ Spot* spot = mLocked.spots.itemAt(i);
+ fadeOutAndReleaseSpotLocked(spot);
+ }
+}
+
+void PointerController::loadResources() {
+ mPolicy->loadPointerResources(&mResources);
+}
+
+
+// --- PointerController::Spot ---
+
+void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
+ sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
+ sprite->setAlpha(alpha);
+ sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
+ sprite->setPosition(x, y);
+
+ this->x = x;
+ this->y = y;
+
+ if (icon != lastIcon) {
+ lastIcon = icon;
+ if (icon) {
+ sprite->setIcon(*icon);
+ sprite->setVisible(true);
+ } else {
+ sprite->setVisible(false);
+ }
+ }
}
} // namespace android
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index d467a5a..b9184ac 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -30,7 +30,10 @@
namespace android {
/**
- * Interface for tracking a single (mouse) pointer.
+ * Interface for tracking a mouse / touch pad pointer and touch pad spots.
+ *
+ * The spots are sprites on screen that visually represent the positions of
+ * fingers
*
* The pointer controller is responsible for providing synchronization and for tracking
* display orientation changes if needed.
@@ -64,8 +67,98 @@
/* Fades the pointer out now. */
virtual void fade() = 0;
- /* Makes the pointer visible if it has faded out. */
+ /* Makes the pointer visible if it has faded out.
+ * The pointer never unfades itself automatically. This method must be called
+ * by the client whenever the pointer is moved or a button is pressed and it
+ * wants to ensure that the pointer becomes visible again. */
virtual void unfade() = 0;
+
+ enum Presentation {
+ // Show the mouse pointer.
+ PRESENTATION_POINTER,
+ // Show spots and a spot anchor in place of the mouse pointer.
+ PRESENTATION_SPOT,
+ };
+
+ /* Sets the mode of the pointer controller. */
+ virtual void setPresentation(Presentation presentation) = 0;
+
+ // Describes the current gesture.
+ enum SpotGesture {
+ // No gesture.
+ // Do not display any spots.
+ SPOT_GESTURE_NEUTRAL,
+ // Tap at current location.
+ // Briefly display one spot at the tapped location.
+ SPOT_GESTURE_TAP,
+ // Drag at current location.
+ // Display spot at pressed location.
+ SPOT_GESTURE_DRAG,
+ // Button pressed but no finger is down.
+ // Display spot at pressed location.
+ SPOT_GESTURE_BUTTON_CLICK,
+ // Button pressed and a finger is down.
+ // Display spot at pressed location.
+ SPOT_GESTURE_BUTTON_DRAG,
+ // One finger down and hovering.
+ // Display spot at the hovered location.
+ SPOT_GESTURE_HOVER,
+ // Two fingers down but not sure in which direction they are moving so we consider
+ // it a press at the pointer location.
+ // Display two spots near the pointer location.
+ SPOT_GESTURE_PRESS,
+ // Two fingers down and moving in same direction.
+ // Display two spots near the pointer location.
+ SPOT_GESTURE_SWIPE,
+ // Two or more fingers down and moving in arbitrary directions.
+ // Display two or more spots near the pointer location, one for each finger.
+ SPOT_GESTURE_FREEFORM,
+ };
+
+ /* Sets the spots for the current gesture.
+ * The spots are not subject to the inactivity timeout like the pointer
+ * itself it since they are expected to remain visible for so long as
+ * the fingers are on the touch pad.
+ *
+ * The values of the AMOTION_EVENT_AXIS_PRESSURE axis is significant.
+ * For spotCoords, pressure != 0 indicates that the spot's location is being
+ * pressed (not hovering).
+ */
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
+ BitSet32 spotIdBits) = 0;
+
+ /* Removes all spots. */
+ virtual void clearSpots() = 0;
+};
+
+
+/*
+ * Pointer resources.
+ */
+struct PointerResources {
+ SpriteIcon spotHover;
+ SpriteIcon spotTouch;
+ SpriteIcon spotAnchor;
+};
+
+
+/*
+ * Pointer controller policy interface.
+ *
+ * The pointer controller policy is used by the pointer controller to interact with
+ * the Window Manager and other system components.
+ *
+ * The actual implementation is partially supported by callbacks into the DVM
+ * via JNI. This interface is also mocked in the unit tests.
+ */
+class PointerControllerPolicyInterface : public virtual RefBase {
+protected:
+ PointerControllerPolicyInterface() { }
+ virtual ~PointerControllerPolicyInterface() { }
+
+public:
+ virtual void loadPointerResources(PointerResources* outResources) = 0;
};
@@ -79,12 +172,13 @@
virtual ~PointerController();
public:
- enum InactivityFadeDelay {
- INACTIVITY_FADE_DELAY_NORMAL = 0,
- INACTIVITY_FADE_DELAY_SHORT = 1,
+ enum InactivityTimeout {
+ INACTIVITY_TIMEOUT_NORMAL = 0,
+ INACTIVITY_TIMEOUT_SHORT = 1,
};
- PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+ PointerController(const sp<PointerControllerPolicyInterface>& policy,
+ const sp<Looper>& looper, const sp<SpriteController>& spriteController);
virtual bool getBounds(float* outMinX, float* outMinY,
float* outMaxX, float* outMaxY) const;
@@ -96,51 +190,101 @@
virtual void fade();
virtual void unfade();
+ virtual void setPresentation(Presentation presentation);
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits);
+ virtual void clearSpots();
+
void setDisplaySize(int32_t width, int32_t height);
void setDisplayOrientation(int32_t orientation);
- void setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
- void setInactivityFadeDelay(InactivityFadeDelay inactivityFadeDelay);
+ void setPointerIcon(const SpriteIcon& icon);
+ void setInactivityTimeout(InactivityTimeout inactivityTimeout);
private:
+ static const size_t MAX_RECYCLED_SPRITES = 12;
+ static const size_t MAX_SPOTS = 12;
+
enum {
- MSG_FADE_STEP = 0,
+ MSG_ANIMATE,
+ MSG_INACTIVITY_TIMEOUT,
+ };
+
+ struct Spot {
+ static const uint32_t INVALID_ID = 0xffffffff;
+
+ uint32_t id;
+ sp<Sprite> sprite;
+ float alpha;
+ float scale;
+ float x, y;
+
+ inline Spot(uint32_t id, const sp<Sprite>& sprite)
+ : id(id), sprite(sprite), alpha(1.0f), scale(1.0f),
+ x(0.0f), y(0.0f), lastIcon(NULL) { }
+
+ void updateSprite(const SpriteIcon* icon, float x, float y);
+
+ private:
+ const SpriteIcon* lastIcon;
};
mutable Mutex mLock;
+ sp<PointerControllerPolicyInterface> mPolicy;
sp<Looper> mLooper;
sp<SpriteController> mSpriteController;
sp<WeakMessageHandler> mHandler;
+ PointerResources mResources;
+
struct Locked {
+ bool animationPending;
+ nsecs_t animationTime;
+
int32_t displayWidth;
int32_t displayHeight;
int32_t displayOrientation;
+ InactivityTimeout inactivityTimeout;
+
+ Presentation presentation;
+ bool presentationChanged;
+
+ bool pointerIsFading;
float pointerX;
float pointerY;
+ float pointerAlpha;
+ sp<Sprite> pointerSprite;
+ SpriteIcon pointerIcon;
+ bool pointerIconChanged;
+
uint32_t buttonState;
- float fadeAlpha;
- InactivityFadeDelay inactivityFadeDelay;
-
- bool visible;
-
- sp<Sprite> sprite;
+ Vector<Spot*> spots;
+ Vector<sp<Sprite> > recycledSprites;
} mLocked;
bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
void setPositionLocked(float x, float y);
- void updateLocked();
void handleMessage(const Message& message);
- bool unfadeBeforeUpdateLocked();
- void startFadeLocked();
- void startInactivityFadeDelayLocked();
- void fadeStepLocked();
- bool isFadingLocked();
- nsecs_t getInactivityFadeDelayTimeLocked();
- void sendFadeStepMessageDelayedLocked(nsecs_t delayTime);
+ void doAnimate();
+ void doInactivityTimeout();
+
+ void startAnimationLocked();
+
+ void resetInactivityTimeoutLocked();
+ void sendImmediateInactivityTimeoutLocked();
+ void updatePointerLocked();
+
+ Spot* getSpotLocked(uint32_t id);
+ Spot* createAndAddSpotLocked(uint32_t id);
+ Spot* removeFirstFadingSpotLocked();
+ void releaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseSpotLocked(Spot* spot);
+ void fadeOutAndReleaseAllSpotsLocked();
+
+ void loadResources();
};
} // namespace android
diff --git a/services/input/SpotController.cpp b/services/input/SpotController.cpp
deleted file mode 100644
index dffad81..0000000
--- a/services/input/SpotController.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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_TAG "SpotController"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages about spot updates
-#define DEBUG_SPOT_UPDATES 0
-
-#include "SpotController.h"
-
-#include <cutils/log.h>
-
-namespace android {
-
-// --- SpotController ---
-
-SpotController::SpotController(const sp<Looper>& looper,
- const sp<SpriteController>& spriteController) :
- mLooper(looper), mSpriteController(spriteController) {
- mHandler = new WeakMessageHandler(this);
-}
-
-SpotController::~SpotController() {
- mLooper->removeMessages(mHandler);
-}
-
-void SpotController:: handleMessage(const Message& message) {
-}
-
-} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
deleted file mode 100644
index 1d091d7..0000000
--- a/services/input/SpotController.h
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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 _UI_SPOT_CONTROLLER_H
-#define _UI_SPOT_CONTROLLER_H
-
-#include "SpriteController.h"
-
-#include <utils/RefBase.h>
-#include <utils/Looper.h>
-
-#include <SkBitmap.h>
-
-namespace android {
-
-/*
- * Interface for displaying spots on screen that visually represent the positions
- * of fingers on a touch pad.
- *
- * The spot controller is responsible for providing synchronization and for tracking
- * display orientation changes if needed.
- */
-class SpotControllerInterface : public virtual RefBase {
-protected:
- SpotControllerInterface() { }
- virtual ~SpotControllerInterface() { }
-
-public:
-
-};
-
-
-/*
- * Sprite-based spot controller implementation.
- */
-class SpotController : public SpotControllerInterface, public MessageHandler {
-protected:
- virtual ~SpotController();
-
-public:
- SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
-
-private:
- mutable Mutex mLock;
-
- sp<Looper> mLooper;
- sp<SpriteController> mSpriteController;
- sp<WeakMessageHandler> mHandler;
-
- struct Locked {
- } mLocked;
-
- void handleMessage(const Message& message);
-};
-
-} // namespace android
-
-#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
index c6d4390f..2fd1f0a 100644
--- a/services/input/SpriteController.cpp
+++ b/services/input/SpriteController.cpp
@@ -36,6 +36,9 @@
SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
mLooper(looper), mOverlayLayer(overlayLayer) {
mHandler = new WeakMessageHandler(this);
+
+ mLocked.transactionNestingCount = 0;
+ mLocked.deferredSpriteUpdate = false;
}
SpriteController::~SpriteController() {
@@ -51,17 +54,40 @@
return new SpriteImpl(this);
}
-void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
- bool wasEmpty = mInvalidatedSprites.isEmpty();
- mInvalidatedSprites.push(sprite);
- if (wasEmpty) {
+void SpriteController::openTransaction() {
+ AutoMutex _l(mLock);
+
+ mLocked.transactionNestingCount += 1;
+}
+
+void SpriteController::closeTransaction() {
+ AutoMutex _l(mLock);
+
+ LOG_ALWAYS_FATAL_IF(mLocked.transactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mLocked.transactionNestingCount -= 1;
+ if (mLocked.transactionNestingCount == 0 && mLocked.deferredSpriteUpdate) {
+ mLocked.deferredSpriteUpdate = false;
mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
}
}
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+ bool wasEmpty = mLocked.invalidatedSprites.isEmpty();
+ mLocked.invalidatedSprites.push(sprite);
+ if (wasEmpty) {
+ if (mLocked.transactionNestingCount != 0) {
+ mLocked.deferredSpriteUpdate = true;
+ } else {
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
+ }
+}
+
void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
- bool wasEmpty = mDisposedSurfaces.isEmpty();
- mDisposedSurfaces.push(surfaceControl);
+ bool wasEmpty = mLocked.disposedSurfaces.isEmpty();
+ mLocked.disposedSurfaces.push(surfaceControl);
if (wasEmpty) {
mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
}
@@ -89,14 +115,14 @@
{ // acquire lock
AutoMutex _l(mLock);
- numSprites = mInvalidatedSprites.size();
+ numSprites = mLocked.invalidatedSprites.size();
for (size_t i = 0; i < numSprites; i++) {
- const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+ const sp<SpriteImpl>& sprite = mLocked.invalidatedSprites.itemAt(i);
updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
sprite->resetDirtyLocked();
}
- mInvalidatedSprites.clear();
+ mLocked.invalidatedSprites.clear();
} // release lock
// Create missing surfaces.
@@ -105,8 +131,8 @@
SpriteUpdate& update = updates.editItemAt(i);
if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
- update.state.surfaceWidth = update.state.bitmap.width();
- update.state.surfaceHeight = update.state.bitmap.height();
+ update.state.surfaceWidth = update.state.icon.bitmap.width();
+ update.state.surfaceHeight = update.state.icon.bitmap.height();
update.state.surfaceDrawn = false;
update.state.surfaceVisible = false;
update.state.surfaceControl = obtainSurface(
@@ -123,8 +149,8 @@
SpriteUpdate& update = updates.editItemAt(i);
if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
- int32_t desiredWidth = update.state.bitmap.width();
- int32_t desiredHeight = update.state.bitmap.height();
+ int32_t desiredWidth = update.state.icon.bitmap.width();
+ int32_t desiredHeight = update.state.icon.bitmap.height();
if (update.state.surfaceWidth < desiredWidth
|| update.state.surfaceHeight < desiredHeight) {
if (!haveGlobalTransaction) {
@@ -187,16 +213,16 @@
SkPaint paint;
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+ surfaceCanvas.drawBitmap(update.state.icon.bitmap, 0, 0, &paint);
- if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+ if (surfaceInfo.w > uint32_t(update.state.icon.bitmap.width())) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
- surfaceInfo.w, update.state.bitmap.height(), paint);
+ surfaceCanvas.drawRectCoords(update.state.icon.bitmap.width(), 0,
+ surfaceInfo.w, update.state.icon.bitmap.height(), paint);
}
- if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+ if (surfaceInfo.h > uint32_t(update.state.icon.bitmap.height())) {
paint.setColor(0); // transparent fill color
- surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+ surfaceCanvas.drawRectCoords(0, update.state.icon.bitmap.height(),
surfaceInfo.w, surfaceInfo.h, paint);
}
@@ -246,8 +272,8 @@
&& (becomingVisible || (update.state.dirty & (DIRTY_POSITION
| DIRTY_HOTSPOT)))) {
status = update.state.surfaceControl->setPosition(
- update.state.positionX - update.state.hotSpotX,
- update.state.positionY - update.state.hotSpotY);
+ update.state.positionX - update.state.icon.hotSpotX,
+ update.state.positionY - update.state.icon.hotSpotY);
if (status) {
LOGE("Error %d setting sprite surface position.", status);
}
@@ -329,8 +355,10 @@
// Collect disposed surfaces.
Vector<sp<SurfaceControl> > disposedSurfaces;
{ // acquire lock
- disposedSurfaces = mDisposedSurfaces;
- mDisposedSurfaces.clear();
+ AutoMutex _l(mLock);
+
+ disposedSurfaces = mLocked.disposedSurfaces;
+ mLocked.disposedSurfaces.clear();
} // release lock
// Release the last reference to each surface outside of the lock.
@@ -349,7 +377,8 @@
sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
- if (surfaceControl == NULL) {
+ if (surfaceControl == NULL || !surfaceControl->isValid()
+ || !surfaceControl->getSurface()->isValid()) {
LOGE("Error creating sprite surface.");
return NULL;
}
@@ -360,7 +389,7 @@
// --- SpriteController::SpriteImpl ---
SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
- mController(controller), mTransactionNestingCount(0) {
+ mController(controller) {
}
SpriteController::SpriteImpl::~SpriteImpl() {
@@ -368,27 +397,33 @@
// Let the controller take care of deleting the last reference to sprite
// surfaces so that we do not block the caller on an IPC here.
- if (mState.surfaceControl != NULL) {
- mController->disposeSurfaceLocked(mState.surfaceControl);
- mState.surfaceControl.clear();
+ if (mLocked.state.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mLocked.state.surfaceControl);
+ mLocked.state.surfaceControl.clear();
}
}
-void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
- float hotSpotX, float hotSpotY) {
+void SpriteController::SpriteImpl::setIcon(const SpriteIcon& icon) {
AutoMutex _l(mController->mLock);
- if (bitmap) {
- bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
- } else {
- mState.bitmap.reset();
- }
+ uint32_t dirty;
+ if (icon.isValid()) {
+ icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config);
- uint32_t dirty = DIRTY_BITMAP;
- if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
- mState.hotSpotX = hotSpotX;
- mState.hotSpotY = hotSpotY;
- dirty |= DIRTY_HOTSPOT;
+ if (!mLocked.state.icon.isValid()
+ || mLocked.state.icon.hotSpotX != icon.hotSpotX
+ || mLocked.state.icon.hotSpotY != icon.hotSpotY) {
+ mLocked.state.icon.hotSpotX = icon.hotSpotX;
+ mLocked.state.icon.hotSpotY = icon.hotSpotY;
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ dirty = DIRTY_BITMAP;
+ }
+ } else if (mLocked.state.icon.isValid()) {
+ mLocked.state.icon.bitmap.reset();
+ dirty = DIRTY_BITMAP | DIRTY_HOTSPOT;
+ } else {
+ return; // setting to invalid icon and already invalid so nothing to do
}
invalidateLocked(dirty);
@@ -397,8 +432,8 @@
void SpriteController::SpriteImpl::setVisible(bool visible) {
AutoMutex _l(mController->mLock);
- if (mState.visible != visible) {
- mState.visible = visible;
+ if (mLocked.state.visible != visible) {
+ mLocked.state.visible = visible;
invalidateLocked(DIRTY_VISIBILITY);
}
}
@@ -406,9 +441,9 @@
void SpriteController::SpriteImpl::setPosition(float x, float y) {
AutoMutex _l(mController->mLock);
- if (mState.positionX != x || mState.positionY != y) {
- mState.positionX = x;
- mState.positionY = y;
+ if (mLocked.state.positionX != x || mLocked.state.positionY != y) {
+ mLocked.state.positionX = x;
+ mLocked.state.positionY = y;
invalidateLocked(DIRTY_POSITION);
}
}
@@ -416,8 +451,8 @@
void SpriteController::SpriteImpl::setLayer(int32_t layer) {
AutoMutex _l(mController->mLock);
- if (mState.layer != layer) {
- mState.layer = layer;
+ if (mLocked.state.layer != layer) {
+ mLocked.state.layer = layer;
invalidateLocked(DIRTY_LAYER);
}
}
@@ -425,8 +460,8 @@
void SpriteController::SpriteImpl::setAlpha(float alpha) {
AutoMutex _l(mController->mLock);
- if (mState.alpha != alpha) {
- mState.alpha = alpha;
+ if (mLocked.state.alpha != alpha) {
+ mLocked.state.alpha = alpha;
invalidateLocked(DIRTY_ALPHA);
}
}
@@ -435,37 +470,18 @@
const SpriteTransformationMatrix& matrix) {
AutoMutex _l(mController->mLock);
- if (mState.transformationMatrix != matrix) {
- mState.transformationMatrix = matrix;
+ if (mLocked.state.transformationMatrix != matrix) {
+ mLocked.state.transformationMatrix = matrix;
invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
}
}
-void SpriteController::SpriteImpl::openTransaction() {
- AutoMutex _l(mController->mLock);
-
- mTransactionNestingCount += 1;
-}
-
-void SpriteController::SpriteImpl::closeTransaction() {
- AutoMutex _l(mController->mLock);
-
- LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
- "Sprite closeTransaction() called but there is no open sprite transaction");
-
- mTransactionNestingCount -= 1;
- if (mTransactionNestingCount == 0 && mState.dirty) {
- mController->invalidateSpriteLocked(this);
- }
-}
-
void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
- if (mTransactionNestingCount > 0) {
- bool wasDirty = mState.dirty;
- mState.dirty |= dirty;
- if (!wasDirty) {
- mController->invalidateSpriteLocked(this);
- }
+ bool wasDirty = mLocked.state.dirty;
+ mLocked.state.dirty |= dirty;
+
+ if (!wasDirty) {
+ mController->invalidateSpriteLocked(this);
}
}
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
index 27afb5e..50ae8a5 100644
--- a/services/input/SpriteController.h
+++ b/services/input/SpriteController.h
@@ -33,6 +33,8 @@
*/
struct SpriteTransformationMatrix {
inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+ inline SpriteTransformationMatrix(float dsdx, float dtdx, float dsdy, float dtdy) :
+ dsdx(dsdx), dtdx(dtdx), dsdy(dsdy), dtdy(dtdy) { }
float dsdx;
float dtdx;
@@ -52,6 +54,35 @@
};
/*
+ * Icon that a sprite displays, including its hotspot.
+ */
+struct SpriteIcon {
+ inline SpriteIcon() : hotSpotX(0), hotSpotY(0) { }
+ inline SpriteIcon(const SkBitmap& bitmap, float hotSpotX, float hotSpotY) :
+ bitmap(bitmap), hotSpotX(hotSpotX), hotSpotY(hotSpotY) { }
+
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+
+ inline SpriteIcon copy() const {
+ SkBitmap bitmapCopy;
+ bitmap.copyTo(&bitmapCopy, SkBitmap::kARGB_8888_Config);
+ return SpriteIcon(bitmapCopy, hotSpotX, hotSpotY);
+ }
+
+ inline void reset() {
+ bitmap.reset();
+ hotSpotX = 0;
+ hotSpotY = 0;
+ }
+
+ inline bool isValid() const {
+ return !bitmap.isNull() && !bitmap.empty();
+ }
+};
+
+/*
* A sprite is a simple graphical object that is displayed on-screen above other layers.
* The basic sprite class is an interface.
* The implementation is provided by the sprite controller.
@@ -62,9 +93,21 @@
virtual ~Sprite() { }
public:
+ enum {
+ // The base layer for pointer sprites.
+ BASE_LAYER_POINTER = 0, // reserve space for 1 pointer
+
+ // The base layer for spot sprites.
+ BASE_LAYER_SPOT = 1, // reserve space for MAX_POINTER_ID spots
+ };
+
/* Sets the bitmap that is drawn by the sprite.
* The sprite retains a copy of the bitmap for subsequent rendering. */
- virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+ virtual void setIcon(const SpriteIcon& icon) = 0;
+
+ inline void clearIcon() {
+ setIcon(SpriteIcon());
+ }
/* Sets whether the sprite is visible. */
virtual void setVisible(bool visible) = 0;
@@ -81,14 +124,6 @@
/* Sets the sprite transformation matrix. */
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
-
- /* Opens or closes a transaction to perform a batch of sprite updates as part of
- * a single operation such as setPosition and setAlpha. It is not necessary to
- * open a transaction when updating a single property.
- * Calls to openTransaction() nest and must be matched by an equal number
- * of calls to closeTransaction(). */
- virtual void openTransaction() = 0;
- virtual void closeTransaction() = 0;
};
/*
@@ -112,6 +147,14 @@
/* Creates a new sprite, initially invisible. */
sp<Sprite> createSprite();
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ void openTransaction();
+ void closeTransaction();
+
private:
enum {
MSG_UPDATE_SPRITES,
@@ -135,16 +178,14 @@
* Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
struct SpriteState {
inline SpriteState() :
- dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+ dirty(0), visible(false),
positionX(0), positionY(0), layer(0), alpha(1.0f),
surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
}
uint32_t dirty;
- SkBitmap bitmap;
- float hotSpotX;
- float hotSpotY;
+ SpriteIcon icon;
bool visible;
float positionX;
float positionY;
@@ -159,7 +200,7 @@
bool surfaceVisible;
inline bool wantSurfaceVisible() const {
- return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+ return visible && alpha > 0.0f && icon.isValid();
}
};
@@ -177,37 +218,36 @@
public:
SpriteImpl(const sp<SpriteController> controller);
- virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+ virtual void setIcon(const SpriteIcon& icon);
virtual void setVisible(bool visible);
virtual void setPosition(float x, float y);
virtual void setLayer(int32_t layer);
virtual void setAlpha(float alpha);
virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
- virtual void openTransaction();
- virtual void closeTransaction();
inline const SpriteState& getStateLocked() const {
- return mState;
+ return mLocked.state;
}
inline void resetDirtyLocked() {
- mState.dirty = 0;
+ mLocked.state.dirty = 0;
}
inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
int32_t width, int32_t height, bool drawn, bool visible) {
- mState.surfaceControl = surfaceControl;
- mState.surfaceWidth = width;
- mState.surfaceHeight = height;
- mState.surfaceDrawn = drawn;
- mState.surfaceVisible = visible;
+ mLocked.state.surfaceControl = surfaceControl;
+ mLocked.state.surfaceWidth = width;
+ mLocked.state.surfaceHeight = height;
+ mLocked.state.surfaceDrawn = drawn;
+ mLocked.state.surfaceVisible = visible;
}
private:
sp<SpriteController> mController;
- SpriteState mState; // guarded by mController->mLock
- uint32_t mTransactionNestingCount; // guarded by mController->mLock
+ struct Locked {
+ SpriteState state;
+ } mLocked; // guarded by mController->mLock
void invalidateLocked(uint32_t dirty);
};
@@ -232,8 +272,12 @@
sp<SurfaceComposerClient> mSurfaceComposerClient;
- Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
- Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+ struct Locked {
+ Vector<sp<SpriteImpl> > invalidatedSprites;
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ uint32_t transactionNestingCount;
+ bool deferredSpriteUpdate;
+ } mLocked; // guarded by mLock
void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index ba8ca9c..54bb9d7 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -97,6 +97,16 @@
virtual void unfade() {
}
+
+ virtual void setPresentation(Presentation presentation) {
+ }
+
+ virtual void setSpots(SpotGesture spotGesture,
+ const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
+ }
+
+ virtual void clearSpots() {
+ }
};
@@ -192,10 +202,6 @@
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
return mPointerControllers.valueFor(deviceId);
}
-
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
- return NULL;
- }
};
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 5853696..9ff5233 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -49,8 +49,9 @@
import android.util.Log;
import com.android.internal.telephony.Phone;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -122,7 +123,7 @@
// resampled each time we turn on tethering - used as cache for settings/config-val
private boolean mDunRequired; // configuration info - must use DUN apn on 3g
- private HierarchicalStateMachine mTetherMasterSM;
+ private StateMachine mTetherMasterSM;
private Notification mTetheredNotification;
@@ -668,7 +669,7 @@
}
- class TetherInterfaceSM extends HierarchicalStateMachine {
+ class TetherInterfaceSM extends StateMachine {
// notification from the master SM that it's not in tether mode
static final int CMD_TETHER_MODE_DEAD = 1;
// request from the user that it wants to tether
@@ -694,13 +695,13 @@
// the upstream connection has changed
static final int CMD_TETHER_CONNECTION_CHANGED = 12;
- private HierarchicalState mDefaultState;
+ private State mDefaultState;
- private HierarchicalState mInitialState;
- private HierarchicalState mStartingState;
- private HierarchicalState mTetheredState;
+ private State mInitialState;
+ private State mStartingState;
+ private State mTetheredState;
- private HierarchicalState mUnavailableState;
+ private State mUnavailableState;
private boolean mAvailable;
private boolean mTethered;
@@ -732,7 +733,7 @@
public String toString() {
String res = new String();
res += mIfaceName + " - ";
- HierarchicalState current = getCurrentState();
+ IState current = getCurrentState();
if (current == mInitialState) res += "InitialState";
if (current == mStartingState) res += "StartingState";
if (current == mTetheredState) res += "TetheredState";
@@ -782,7 +783,7 @@
return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
}
- class InitialState extends HierarchicalState {
+ class InitialState extends State {
@Override
public void enter() {
setAvailable(true);
@@ -812,7 +813,7 @@
}
}
- class StartingState extends HierarchicalState {
+ class StartingState extends State {
@Override
public void enter() {
setAvailable(false);
@@ -870,7 +871,7 @@
}
}
- class TetheredState extends HierarchicalState {
+ class TetheredState extends State {
@Override
public void enter() {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
@@ -1034,7 +1035,7 @@
}
}
- class UnavailableState extends HierarchicalState {
+ class UnavailableState extends State {
@Override
public void enter() {
setAvailable(false);
@@ -1064,7 +1065,7 @@
}
- class TetherMasterSM extends HierarchicalStateMachine {
+ class TetherMasterSM extends StateMachine {
// an interface SM has requested Tethering
static final int CMD_TETHER_MODE_REQUESTED = 1;
// an interface SM has unrequested Tethering
@@ -1082,14 +1083,14 @@
// We do not flush the old ones.
private int mSequenceNumber;
- private HierarchicalState mInitialState;
- private HierarchicalState mTetherModeAliveState;
+ private State mInitialState;
+ private State mTetherModeAliveState;
- private HierarchicalState mSetIpForwardingEnabledErrorState;
- private HierarchicalState mSetIpForwardingDisabledErrorState;
- private HierarchicalState mStartTetheringErrorState;
- private HierarchicalState mStopTetheringErrorState;
- private HierarchicalState mSetDnsForwardersErrorState;
+ private State mSetIpForwardingEnabledErrorState;
+ private State mSetIpForwardingDisabledErrorState;
+ private State mStartTetheringErrorState;
+ private State mStopTetheringErrorState;
+ private State mSetDnsForwardersErrorState;
private ArrayList mNotifyList;
@@ -1125,7 +1126,7 @@
setInitialState(mInitialState);
}
- class TetherMasterUtilState extends HierarchicalState {
+ class TetherMasterUtilState extends State {
protected final static boolean TRY_TO_SETUP_MOBILE_CONNECTION = true;
protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE = false;
@@ -1440,7 +1441,7 @@
}
}
- class ErrorState extends HierarchicalState {
+ class ErrorState extends State {
int mErrorNotification;
@Override
public boolean processMessage(Message message) {
diff --git a/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java
index da3ebaf..d10aa97 100644
--- a/services/java/com/android/server/pm/Installer.java
+++ b/services/java/com/android/server/pm/Installer.java
@@ -225,10 +225,12 @@
return execute(builder.toString());
}
- public int remove(String name) {
+ public int remove(String name, int userId) {
StringBuilder builder = new StringBuilder("remove");
builder.append(' ');
builder.append(name);
+ builder.append(' ');
+ builder.append(userId);
return execute(builder.toString());
}
@@ -248,10 +250,30 @@
return execute(builder.toString());
}
- public int clearUserData(String name) {
+ public int createUserData(String name, int uid, int userId) {
+ StringBuilder builder = new StringBuilder("mkuserdata");
+ builder.append(' ');
+ builder.append(name);
+ builder.append(' ');
+ builder.append(uid);
+ builder.append(' ');
+ builder.append(userId);
+ return execute(builder.toString());
+ }
+
+ public int removeUserDataDirs(int userId) {
+ StringBuilder builder = new StringBuilder("rmuser");
+ builder.append(' ');
+ builder.append(userId);
+ return execute(builder.toString());
+ }
+
+ public int clearUserData(String name, int userId) {
StringBuilder builder = new StringBuilder("rmuserdata");
builder.append(' ');
builder.append(name);
+ builder.append(' ');
+ builder.append(userId);
return execute(builder.toString());
}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index a9d49b4..6e1093f 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -65,6 +65,7 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
+import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -208,6 +209,9 @@
// This is where all application persistent data goes.
final File mAppDataDir;
+ // This is where all application persistent data goes for secondary users.
+ final File mUserAppDataDir;
+
// This is the object monitoring the framework dir.
final FileObserver mFrameworkInstallObserver;
@@ -359,6 +363,8 @@
// Delay time in millisecs
static final int BROADCAST_DELAY = 10 * 1000;
+ final UserManager mUserManager;
+
final private DefaultContainerConnection mDefContainerConn =
new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection {
@@ -797,8 +803,11 @@
File dataDir = Environment.getDataDirectory();
mAppDataDir = new File(dataDir, "data");
+ mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
+ mUserManager = new UserManager(mInstaller, mUserAppDataDir);
+
if (mInstaller == null) {
// Make sure these dirs exist, when we are running in
// the simulator.
@@ -806,6 +815,7 @@
File miscDir = new File(dataDir, "misc");
miscDir.mkdirs();
mAppDataDir.mkdirs();
+ mUserAppDataDir.mkdirs();
mDrmAppPrivateInstallDir.mkdirs();
}
@@ -974,7 +984,8 @@
+ " no longer exists; wiping its data";
reportSettingsProblem(Log.WARN, msg);
if (mInstaller != null) {
- mInstaller.remove(ps.name);
+ mInstaller.remove(ps.name, 0);
+ mUserManager.removePackageForAllUsers(ps.name);
}
}
}
@@ -1059,10 +1070,12 @@
void cleanupInstallFailedPackage(PackageSetting ps) {
Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
if (mInstaller != null) {
- int retCode = mInstaller.remove(ps.name);
+ int retCode = mInstaller.remove(ps.name, 0);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove app data directory for package: "
+ ps.name + ", retcode=" + retCode);
+ } else {
+ mUserManager.removePackageForAllUsers(ps.name);
}
} else {
//for emulator
@@ -1510,7 +1523,8 @@
ps.pkg.applicationInfo.flags = ps.pkgFlags;
ps.pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
ps.pkg.applicationInfo.sourceDir = ps.codePathString;
- ps.pkg.applicationInfo.dataDir = getDataPathForPackage(ps.pkg).getPath();
+ ps.pkg.applicationInfo.dataDir =
+ getDataPathForPackage(ps.pkg.packageName, 0).getPath();
ps.pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
ps.pkg.mSetEnabled = ps.enabled;
ps.pkg.mSetStopped = ps.stopped;
@@ -2836,11 +2850,15 @@
return true;
}
- private File getDataPathForPackage(PackageParser.Package pkg) {
- final File dataPath = new File(mAppDataDir, pkg.packageName);
- return dataPath;
+ File getDataPathForUser(int userId) {
+ return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId);
}
-
+
+ private File getDataPathForPackage(String packageName, int userId) {
+ return new File(mUserAppDataDir.getAbsolutePath() + File.separator
+ + userId + File.separator + packageName);
+ }
+
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime) {
File scanFile = new File(pkg.mScanPath);
@@ -3162,7 +3180,7 @@
pkg.applicationInfo.dataDir = dataPath.getPath();
} else {
// This is a normal package, need to make its data directory.
- dataPath = getDataPathForPackage(pkg);
+ dataPath = getDataPathForPackage(pkg.packageName, 0);
boolean uidError = false;
@@ -3178,8 +3196,11 @@
// If this is a system app, we can at least delete its
// current data so the application will still work.
if (mInstaller != null) {
- int ret = mInstaller.remove(pkgName);
+ int ret = mInstaller.remove(pkgName, 0);
if (ret >= 0) {
+ // TODO: Kill the processes first
+ // Remove the data directories for all users
+ mUserManager.removePackageForAllUsers(pkgName);
// Old data gone!
String msg = "System package " + pkg.packageName
+ " has changed from uid: "
@@ -3199,6 +3220,9 @@
mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return null;
}
+ // Create data directories for all users
+ mUserManager.installPackageForAllUsers(pkgName,
+ pkg.applicationInfo.uid);
}
}
if (!recovered) {
@@ -3235,11 +3259,13 @@
if (mInstaller != null) {
int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.uid);
- if(ret < 0) {
+ if (ret < 0) {
// Error from installer
mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
return null;
}
+ // Create data directories for all users
+ mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid);
} else {
dataPath.mkdirs();
if (dataPath.exists()) {
@@ -5703,7 +5729,7 @@
// Remember this for later, in case we need to rollback this install
String pkgName = pkg.packageName;
- boolean dataDirExists = getDataPathForPackage(pkg).exists();
+ boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
res.name = pkgName;
synchronized(mPackages) {
if (mSettings.mRenamedPackages.containsKey(pkgName)) {
@@ -6390,11 +6416,14 @@
}
if ((flags&PackageManager.DONT_DELETE_DATA) == 0) {
if (mInstaller != null) {
- int retCode = mInstaller.remove(packageName);
+ int retCode = mInstaller.remove(packageName, 0);
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove app data or cache directory for package: "
+ packageName + ", retcode=" + retCode);
// we don't consider this to be a failure of the core package deletion
+ } else {
+ // TODO: Kill the processes first
+ mUserManager.removePackageForAllUsers(packageName);
}
} else {
// for simulator
@@ -6654,7 +6683,7 @@
}
}
if (mInstaller != null) {
- int retCode = mInstaller.clearUserData(packageName);
+ int retCode = mInstaller.clearUserData(packageName, 0); // TODO - correct userId
if (retCode < 0) {
Slog.w(TAG, "Couldn't remove cache files for package: "
+ packageName);
@@ -8015,4 +8044,17 @@
android.provider.Settings.Secure.DEFAULT_INSTALL_LOCATION,
PackageHelper.APP_INSTALL_AUTO);
}
+
+ public UserInfo createUser(String name, int flags) {
+ UserInfo userInfo = mUserManager.createUser(name, flags, getInstalledApplications(0));
+ return userInfo;
+ }
+
+ public boolean removeUser(int userId) {
+ if (userId == 0) {
+ return false;
+ }
+ mUserManager.removeUser(userId);
+ return true;
+ }
}
diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserManager.java
similarity index 65%
rename from services/java/com/android/server/pm/UserDetails.java
rename to services/java/com/android/server/pm/UserManager.java
index 2aeed7c..76fa5ab 100644
--- a/services/java/com/android/server/pm/UserDetails.java
+++ b/services/java/com/android/server/pm/UserManager.java
@@ -18,9 +18,13 @@
import com.android.internal.util.FastXmlSerializer;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.SystemClock;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.util.Xml;
@@ -37,7 +41,7 @@
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
-public class UserDetails {
+public class UserManager {
private static final String TAG_NAME = "name";
private static final String ATTR_FLAGS = "flags";
@@ -48,22 +52,27 @@
private static final String TAG_USER = "user";
- private static final String TAG = "UserDetails";
+ private static final String LOG_TAG = "UserManager";
- private static final String USER_INFO_DIR = "system/users";
+ private static final String USER_INFO_DIR = "system" + File.separator + "users";
private static final String USER_LIST_FILENAME = "userlist.xml";
private SparseArray<UserInfo> mUsers;
private final File mUsersDir;
private final File mUserListFile;
+ private int[] mUserIds;
+
+ private Installer mInstaller;
+ private File mBaseUserPath;
/**
* Available for testing purposes.
*/
- UserDetails(File dataDir) {
+ UserManager(File dataDir, File baseUserPath) {
mUsersDir = new File(dataDir, USER_INFO_DIR);
mUsersDir.mkdirs();
+ mBaseUserPath = baseUserPath;
FileUtils.setPermissions(mUsersDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
@@ -72,8 +81,9 @@
readUserList();
}
- public UserDetails() {
- this(Environment.getDataDirectory());
+ public UserManager(Installer installer, File baseUserPath) {
+ this(Environment.getDataDirectory(), baseUserPath);
+ mInstaller = installer;
}
public List<UserInfo> getUsers() {
@@ -84,6 +94,15 @@
return users;
}
+ /**
+ * Returns an array of user ids. This array is cached here for quick access, so do not modify or
+ * cache it elsewhere.
+ * @return the array of user ids.
+ */
+ int[] getUserIds() {
+ return mUserIds;
+ }
+
private void readUserList() {
mUsers = new SparseArray<UserInfo>();
if (!mUserListFile.exists()) {
@@ -102,7 +121,7 @@
}
if (type != XmlPullParser.START_TAG) {
- Slog.e(TAG, "Unable to read user list");
+ Slog.e(LOG_TAG, "Unable to read user list");
fallbackToSingleUser();
return;
}
@@ -116,6 +135,7 @@
}
}
}
+ updateUserIds();
} catch (IOException ioe) {
fallbackToSingleUser();
} catch (XmlPullParserException pe) {
@@ -128,6 +148,7 @@
UserInfo primary = new UserInfo(0, "Primary",
UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
mUsers.put(0, primary);
+ updateUserIds();
writeUserList();
writeUser(primary);
@@ -164,7 +185,7 @@
serializer.endDocument();
} catch (IOException ioe) {
- Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+ Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
}
}
@@ -194,14 +215,13 @@
serializer.startTag(null, TAG_USER);
serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
serializer.endTag(null, TAG_USER);
- Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml");
}
serializer.endTag(null, TAG_USERS);
serializer.endDocument();
} catch (IOException ioe) {
- Slog.e(TAG, "Error writing user list");
+ Slog.e(LOG_TAG, "Error writing user list");
}
}
@@ -222,14 +242,14 @@
}
if (type != XmlPullParser.START_TAG) {
- Slog.e(TAG, "Unable to read user " + id);
+ Slog.e(LOG_TAG, "Unable to read user " + id);
return null;
}
if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
String storedId = parser.getAttributeValue(null, ATTR_ID);
if (Integer.parseInt(storedId) != id) {
- Slog.e(TAG, "User id does not match the file name");
+ Slog.e(LOG_TAG, "User id does not match the file name");
return null;
}
String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
@@ -256,18 +276,25 @@
return null;
}
- public UserInfo createUser(String name, int flags) {
- int id = getNextAvailableId();
- UserInfo userInfo = new UserInfo(id, name, flags);
- if (!createPackageFolders(id)) {
+ public UserInfo createUser(String name, int flags, List<ApplicationInfo> apps) {
+ int userId = getNextAvailableId();
+ UserInfo userInfo = new UserInfo(userId, name, flags);
+ File userPath = new File(mBaseUserPath, Integer.toString(userId));
+ if (!createPackageFolders(userId, userPath, apps)) {
return null;
}
- mUsers.put(id, userInfo);
+ mUsers.put(userId, userInfo);
writeUserList();
writeUser(userInfo);
+ updateUserIds();
return userInfo;
}
+ /**
+ * Removes a user and all data directories created for that user. This method should be called
+ * after the user's processes have been terminated.
+ * @param id the user's id
+ */
public void removeUser(int id) {
// Remove from the list
UserInfo userInfo = mUsers.get(id);
@@ -277,11 +304,58 @@
// Remove user file
File userFile = new File(mUsersDir, id + ".xml");
userFile.delete();
+ // Update the user list
writeUserList();
+ // Remove the data directories for all packages for this user
removePackageFolders(id);
+ updateUserIds();
}
}
+ public void installPackageForAllUsers(String packageName, int uid) {
+ for (int userId : mUserIds) {
+ // Don't do it for the primary user, it will become recursive.
+ if (userId == 0)
+ continue;
+ mInstaller.createUserData(packageName, PackageManager.getUid(userId, uid),
+ userId);
+ }
+ }
+
+ public void clearUserDataForAllUsers(String packageName) {
+ for (int userId : mUserIds) {
+ // Don't do it for the primary user, it will become recursive.
+ if (userId == 0)
+ continue;
+ mInstaller.clearUserData(packageName, userId);
+ }
+ }
+
+ public void removePackageForAllUsers(String packageName) {
+ for (int userId : mUserIds) {
+ // Don't do it for the primary user, it will become recursive.
+ if (userId == 0)
+ continue;
+ mInstaller.remove(packageName, userId);
+ }
+ }
+
+ /**
+ * Caches the list of user ids in an array, adjusting the array size when necessary.
+ */
+ private void updateUserIds() {
+ if (mUserIds == null || mUserIds.length != mUsers.size()) {
+ mUserIds = new int[mUsers.size()];
+ }
+ for (int i = 0; i < mUsers.size(); i++) {
+ mUserIds[i] = mUsers.keyAt(i);
+ }
+ }
+
+ /**
+ * Returns the next available user id, filling in any holes in the ids.
+ * @return
+ */
private int getNextAvailableId() {
int i = 0;
while (i < Integer.MAX_VALUE) {
@@ -293,13 +367,35 @@
return i;
}
- private boolean createPackageFolders(int id) {
- // TODO: Create data directories for all the packages for a new user, w/ specified user id.
+ private boolean createPackageFolders(int id, File userPath, final List<ApplicationInfo> apps) {
+ // mInstaller may not be available for unit-tests.
+ if (mInstaller == null || apps == null) return true;
+
+ final long startTime = SystemClock.elapsedRealtime();
+ // Create the user path
+ userPath.mkdir();
+ FileUtils.setPermissions(userPath.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+ | FileUtils.S_IXOTH, -1, -1);
+
+ // Create the individual data directories
+ for (ApplicationInfo app : apps) {
+ if (app.uid > android.os.Process.FIRST_APPLICATION_UID
+ && app.uid < PackageManager.PER_USER_RANGE) {
+ mInstaller.createUserData(app.packageName,
+ PackageManager.getUid(id, app.uid), id);
+ }
+ }
+ final long stopTime = SystemClock.elapsedRealtime();
+ Log.i(LOG_TAG,
+ "Time to create " + apps.size() + " packages = " + (stopTime - startTime) + "ms");
return true;
}
private boolean removePackageFolders(int id) {
- // TODO: Remove all the data directories for the specified user.
+ // mInstaller may not be available for unit-tests.
+ if (mInstaller == null) return true;
+
+ mInstaller.removeUserDataDirs(id);
return true;
}
}
diff --git a/services/java/com/android/server/wm/InputManager.java b/services/java/com/android/server/wm/InputManager.java
index b0978a3..69bde41f 100644
--- a/services/java/com/android/server/wm/InputManager.java
+++ b/services/java/com/android/server/wm/InputManager.java
@@ -23,12 +23,6 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.os.Looper;
import android.os.MessageQueue;
@@ -39,6 +33,7 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.KeyEvent;
+import android.view.PointerIcon;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
@@ -63,7 +58,8 @@
private final Context mContext;
private final WindowManagerService mWindowManagerService;
- private static native void nativeInit(Callbacks callbacks, MessageQueue messageQueue);
+ private static native void nativeInit(Context context,
+ Callbacks callbacks, MessageQueue messageQueue);
private static native void nativeStart();
private static native void nativeSetDisplaySize(int displayId, int width, int height);
private static native void nativeSetDisplayOrientation(int displayId, int rotation);
@@ -133,7 +129,7 @@
Looper looper = windowManagerService.mH.getLooper();
Slog.i(TAG, "Initializing input manager");
- nativeInit(mCallbacks, looper.getQueue());
+ nativeInit(mContext, mCallbacks, looper.getQueue());
}
public void start() {
@@ -435,48 +431,6 @@
}
}
- private static final class PointerIcon {
- public Bitmap bitmap;
- public float hotSpotX;
- public float hotSpotY;
-
- public static PointerIcon load(Resources resources, int resourceId) {
- PointerIcon icon = new PointerIcon();
-
- XmlResourceParser parser = resources.getXml(resourceId);
- final int bitmapRes;
- try {
- XmlUtils.beginDocument(parser, "pointer-icon");
-
- TypedArray a = resources.obtainAttributes(
- parser, com.android.internal.R.styleable.PointerIcon);
- bitmapRes = a.getResourceId(com.android.internal.R.styleable.PointerIcon_bitmap, 0);
- icon.hotSpotX = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotX, 0);
- icon.hotSpotY = a.getFloat(com.android.internal.R.styleable.PointerIcon_hotSpotY, 0);
- a.recycle();
- } catch (Exception ex) {
- Slog.e(TAG, "Exception parsing pointer icon resource.", ex);
- return null;
- } finally {
- parser.close();
- }
-
- if (bitmapRes == 0) {
- Slog.e(TAG, "<pointer-icon> is missing bitmap attribute");
- return null;
- }
-
- Drawable drawable = resources.getDrawable(bitmapRes);
- if (!(drawable instanceof BitmapDrawable)) {
- Slog.e(TAG, "<pointer-icon> bitmap attribute must refer to a bitmap drawable");
- return null;
- }
-
- icon.bitmap = ((BitmapDrawable)drawable).getBitmap();
- return icon;
- }
- }
-
/*
* Callbacks from native.
*/
@@ -641,8 +595,7 @@
@SuppressWarnings("unused")
public PointerIcon getPointerIcon() {
- return PointerIcon.load(mContext.getResources(),
- com.android.internal.R.drawable.pointer_arrow_icon);
+ return PointerIcon.getDefaultIcon(mContext);
}
}
}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index aaa305e..1f10d9c 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,13 +36,13 @@
#include <input/InputManager.h>
#include <input/PointerController.h>
-#include <input/SpotController.h>
#include <input/SpriteController.h>
#include <android_os_MessageQueue.h>
#include <android_view_KeyEvent.h>
#include <android_view_MotionEvent.h>
#include <android_view_InputChannel.h>
+#include <android_view_PointerIcon.h>
#include <android/graphics/GraphicsJNI.h>
#include "com_android_server_PowerManagerService.h"
@@ -101,12 +101,6 @@
jfieldID navigation;
} gConfigurationClassInfo;
-static struct {
- jfieldID bitmap;
- jfieldID hotSpotX;
- jfieldID hotSpotY;
-} gPointerIconClassInfo;
-
// --- Global functions ---
@@ -128,17 +122,30 @@
getInputWindowHandleObjLocalRef(env);
}
+static void loadSystemIconAsSprite(JNIEnv* env, jobject contextObj, int32_t style,
+ SpriteIcon* outSpriteIcon) {
+ PointerIcon pointerIcon;
+ status_t status = android_view_PointerIcon_loadSystemIcon(env,
+ contextObj, style, &pointerIcon);
+ if (!status) {
+ pointerIcon.bitmap.copyTo(&outSpriteIcon->bitmap, SkBitmap::kARGB_8888_Config);
+ outSpriteIcon->hotSpotX = pointerIcon.hotSpotX;
+ outSpriteIcon->hotSpotY = pointerIcon.hotSpotY;
+ }
+}
+
// --- NativeInputManager ---
class NativeInputManager : public virtual RefBase,
public virtual InputReaderPolicyInterface,
- public virtual InputDispatcherPolicyInterface {
+ public virtual InputDispatcherPolicyInterface,
+ public virtual PointerControllerPolicyInterface {
protected:
virtual ~NativeInputManager();
public:
- NativeInputManager(jobject callbacksObj, const sp<Looper>& looper);
+ NativeInputManager(jobject contextObj, jobject callbacksObj, const sp<Looper>& looper);
inline sp<InputManager> getInputManager() const { return mInputManager; }
@@ -165,7 +172,6 @@
virtual nsecs_t getVirtualKeyQuietTime();
virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
- virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -189,9 +195,14 @@
virtual bool checkInjectEventsPermissionNonReentrant(
int32_t injectorPid, int32_t injectorUid);
+ /* --- PointerControllerPolicyInterface implementation --- */
+
+ virtual void loadPointerResources(PointerResources* outResources);
+
private:
sp<InputManager> mInputManager;
+ jobject mContextObj;
jobject mCallbacksObj;
sp<Looper> mLooper;
@@ -223,7 +234,7 @@
wp<PointerController> pointerController;
} mLocked;
- void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
+ void updateInactivityTimeoutLocked(const sp<PointerController>& controller);
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
void ensureSpriteControllerLocked();
@@ -240,13 +251,15 @@
-NativeInputManager::NativeInputManager(jobject callbacksObj, const sp<Looper>& looper) :
+NativeInputManager::NativeInputManager(jobject contextObj,
+ jobject callbacksObj, const sp<Looper>& looper) :
mLooper(looper),
mFilterTouchEvents(-1), mFilterJumpyTouchEvents(-1), mVirtualKeyQuietTime(-1),
mKeyRepeatTimeout(-1), mKeyRepeatDelay(-1),
mMaxEventsPerSecond(-1) {
JNIEnv* env = jniEnv();
+ mContextObj = env->NewGlobalRef(contextObj);
mCallbacksObj = env->NewGlobalRef(callbacksObj);
{
@@ -265,6 +278,7 @@
NativeInputManager::~NativeInputManager() {
JNIEnv* env = jniEnv();
+ env->DeleteGlobalRef(mContextObj);
env->DeleteGlobalRef(mCallbacksObj);
}
@@ -288,9 +302,13 @@
void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_t height) {
if (displayId == 0) {
- AutoMutex _l(mLock);
+ { // acquire lock
+ AutoMutex _l(mLock);
- if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
+ if (mLocked.displayWidth == width && mLocked.displayHeight == height) {
+ return;
+ }
+
mLocked.displayWidth = width;
mLocked.displayHeight = height;
@@ -298,7 +316,7 @@
if (controller != NULL) {
controller->setDisplaySize(width, height);
}
- }
+ } // release lock
}
}
@@ -428,40 +446,33 @@
if (controller == NULL) {
ensureSpriteControllerLocked();
- controller = new PointerController(mLooper, mLocked.spriteController);
+ controller = new PointerController(this, mLooper, mLocked.spriteController);
mLocked.pointerController = controller;
controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
controller->setDisplayOrientation(mLocked.displayOrientation);
JNIEnv* env = jniEnv();
- jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
- if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
- jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
- jfloat iconHotSpotY = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotY);
- jobject iconBitmapObj = env->GetObjectField(iconObj, gPointerIconClassInfo.bitmap);
- if (iconBitmapObj) {
- SkBitmap* iconBitmap = GraphicsJNI::getNativeBitmap(env, iconBitmapObj);
- if (iconBitmap) {
- controller->setPointerIcon(iconBitmap, iconHotSpotX, iconHotSpotY);
- }
- env->DeleteLocalRef(iconBitmapObj);
+ jobject pointerIconObj = env->CallObjectMethod(mCallbacksObj,
+ gCallbacksClassInfo.getPointerIcon);
+ if (!checkAndClearExceptionFromCallback(env, "getPointerIcon")) {
+ PointerIcon pointerIcon;
+ status_t status = android_view_PointerIcon_load(env, pointerIconObj,
+ mContextObj, &pointerIcon);
+ if (!status && !pointerIcon.isNullIcon()) {
+ controller->setPointerIcon(SpriteIcon(pointerIcon.bitmap,
+ pointerIcon.hotSpotX, pointerIcon.hotSpotY));
+ } else {
+ controller->setPointerIcon(SpriteIcon());
}
- env->DeleteLocalRef(iconObj);
+ env->DeleteLocalRef(pointerIconObj);
}
- updateInactivityFadeDelayLocked(controller);
+ updateInactivityTimeoutLocked(controller);
}
return controller;
}
-sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
- AutoMutex _l(mLock);
-
- ensureSpriteControllerLocked();
- return new SpotController(mLooper, mLocked.spriteController);
-}
-
void NativeInputManager::ensureSpriteControllerLocked() {
if (mLocked.spriteController == NULL) {
JNIEnv* env = jniEnv();
@@ -642,16 +653,16 @@
sp<PointerController> controller = mLocked.pointerController.promote();
if (controller != NULL) {
- updateInactivityFadeDelayLocked(controller);
+ updateInactivityTimeoutLocked(controller);
}
}
}
-void NativeInputManager::updateInactivityFadeDelayLocked(const sp<PointerController>& controller) {
+void NativeInputManager::updateInactivityTimeoutLocked(const sp<PointerController>& controller) {
bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN;
- controller->setInactivityFadeDelay(lightsOut
- ? PointerController::INACTIVITY_FADE_DELAY_SHORT
- : PointerController::INACTIVITY_FADE_DELAY_NORMAL);
+ controller->setInactivityTimeout(lightsOut
+ ? PointerController::INACTIVITY_TIMEOUT_SHORT
+ : PointerController::INACTIVITY_TIMEOUT_NORMAL);
}
bool NativeInputManager::isScreenOn() {
@@ -884,6 +895,17 @@
return result;
}
+void NativeInputManager::loadPointerResources(PointerResources* outResources) {
+ JNIEnv* env = jniEnv();
+
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_HOVER,
+ &outResources->spotHover);
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_TOUCH,
+ &outResources->spotTouch);
+ loadSystemIconAsSprite(env, mContextObj, POINTER_ICON_STYLE_SPOT_ANCHOR,
+ &outResources->spotAnchor);
+}
+
// ----------------------------------------------------------------------------
@@ -899,10 +921,10 @@
}
static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
- jobject callbacks, jobject messageQueueObj) {
+ jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
if (gNativeInputManager == NULL) {
sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
- gNativeInputManager = new NativeInputManager(callbacks, looper);
+ gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
} else {
LOGE("Input manager already initialized.");
jniThrowRuntimeException(env, "Input manager already initialized.");
@@ -1246,7 +1268,8 @@
static JNINativeMethod gInputManagerMethods[] = {
/* name, signature, funcPtr */
- { "nativeInit", "(Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
+ { "nativeInit", "(Landroid/content/Context;"
+ "Lcom/android/server/wm/InputManager$Callbacks;Landroid/os/MessageQueue;)V",
(void*) android_server_InputManager_nativeInit },
{ "nativeStart", "()V",
(void*) android_server_InputManager_nativeStart },
@@ -1372,7 +1395,7 @@
"getPointerLayer", "()I");
GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz,
- "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;");
+ "getPointerIcon", "()Landroid/view/PointerIcon;");
// KeyEvent
@@ -1421,19 +1444,6 @@
GET_FIELD_ID(gConfigurationClassInfo.navigation, clazz,
"navigation", "I");
- // PointerIcon
-
- FIND_CLASS(clazz, "com/android/server/wm/InputManager$PointerIcon");
-
- GET_FIELD_ID(gPointerIconClassInfo.bitmap, clazz,
- "bitmap", "Landroid/graphics/Bitmap;");
-
- GET_FIELD_ID(gPointerIconClassInfo.hotSpotX, clazz,
- "hotSpotX", "F");
-
- GET_FIELD_ID(gPointerIconClassInfo.hotSpotY, clazz,
- "hotSpotY", "F");
-
return 0;
}
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
index 64cff96..a774841 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp
@@ -93,7 +93,11 @@
int DisplayHardware::getHeight() const { return mHeight; }
PixelFormat DisplayHardware::getFormat() const { return mFormat; }
uint32_t DisplayHardware::getMaxTextureSize() const { return mMaxTextureSize; }
-uint32_t DisplayHardware::getMaxViewportDims() const { return mMaxViewportDims; }
+
+uint32_t DisplayHardware::getMaxViewportDims() const {
+ return mMaxViewportDims[0] < mMaxViewportDims[1] ?
+ mMaxViewportDims[0] : mMaxViewportDims[1];
+}
void DisplayHardware::init(uint32_t dpy)
{
@@ -228,7 +232,7 @@
eglQueryString(display, EGL_EXTENSIONS));
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
- glGetIntegerv(GL_MAX_VIEWPORT_DIMS, &mMaxViewportDims);
+ glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
#ifdef EGL_ANDROID_swap_rectangle
@@ -260,7 +264,7 @@
LOGI("version : %s", extensions.getVersion());
LOGI("extensions: %s", extensions.getExtension());
LOGI("GL_MAX_TEXTURE_SIZE = %d", mMaxTextureSize);
- LOGI("GL_MAX_VIEWPORT_DIMS = %d", mMaxViewportDims);
+ LOGI("GL_MAX_VIEWPORT_DIMS = %d x %d", mMaxViewportDims[0], mMaxViewportDims[1]);
LOGI("flags = %08x", mFlags);
// Unbind the context from this thread
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardware.h b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
index ee7a2af..cdf89fd 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h
@@ -108,7 +108,7 @@
PixelFormat mFormat;
uint32_t mFlags;
mutable uint32_t mPageFlipCount;
- GLint mMaxViewportDims;
+ GLint mMaxViewportDims[2];
GLint mMaxTextureSize;
HWComposer* mHwc;
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
index 90865da..59b7e5a 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.cpp
@@ -38,23 +38,10 @@
#include "SurfaceFlinger.h"
// ----------------------------------------------------------------------------
-// the sim build doesn't have gettid
-
-#ifndef HAVE_GETTID
-# define gettid getpid
-#endif
-
-// ----------------------------------------------------------------------------
namespace android {
-static char const * kSleepFileName = "/sys/power/wait_for_fb_sleep";
-static char const * kWakeFileName = "/sys/power/wait_for_fb_wake";
-static char const * const kOldSleepFileName = "/sys/android_power/wait_for_fb_sleep";
-static char const * const kOldWakeFileName = "/sys/android_power/wait_for_fb_wake";
-
-// This dir exists if the framebuffer console is present, either built into
-// the kernel or loaded as a module.
-static char const * const kFbconSysDir = "/sys/class/graphics/fbcon";
+static char const * const kSleepFileName = "/sys/power/wait_for_fb_sleep";
+static char const * const kWakeFileName = "/sys/power/wait_for_fb_wake";
// ----------------------------------------------------------------------------
@@ -122,237 +109,13 @@
status_t DisplayHardwareBase::DisplayEventThread::readyToRun()
{
- if (access(kSleepFileName, R_OK) || access(kWakeFileName, R_OK)) {
- if (access(kOldSleepFileName, R_OK) || access(kOldWakeFileName, R_OK)) {
- LOGE("Couldn't open %s or %s", kSleepFileName, kWakeFileName);
- return NO_INIT;
- }
- kSleepFileName = kOldSleepFileName;
- kWakeFileName = kOldWakeFileName;
- }
return NO_ERROR;
}
status_t DisplayHardwareBase::DisplayEventThread::initCheck() const
{
- return (((access(kSleepFileName, R_OK) == 0 &&
- access(kWakeFileName, R_OK) == 0) ||
- (access(kOldSleepFileName, R_OK) == 0 &&
- access(kOldWakeFileName, R_OK) == 0)) &&
- access(kFbconSysDir, F_OK) != 0) ? NO_ERROR : NO_INIT;
-}
-
-// ----------------------------------------------------------------------------
-
-pid_t DisplayHardwareBase::ConsoleManagerThread::sSignalCatcherPid = 0;
-
-DisplayHardwareBase::ConsoleManagerThread::ConsoleManagerThread(
- const sp<SurfaceFlinger>& flinger)
- : DisplayEventThreadBase(flinger), consoleFd(-1)
-{
- sSignalCatcherPid = 0;
-
- // create a new console
- char const * const ttydev = "/dev/tty0";
- int fd = open(ttydev, O_RDWR | O_SYNC);
- if (fd<0) {
- LOGE("Can't open %s", ttydev);
- this->consoleFd = -errno;
- return;
- }
-
- // to make sure that we are in text mode
- int res = ioctl(fd, KDSETMODE, (void*) KD_TEXT);
- if (res<0) {
- LOGE("ioctl(%d, KDSETMODE, ...) failed, res %d (%s)",
- fd, res, strerror(errno));
- }
-
- // get the current console
- struct vt_stat vs;
- res = ioctl(fd, VT_GETSTATE, &vs);
- if (res<0) {
- LOGE("ioctl(%d, VT_GETSTATE, ...) failed, res %d (%s)",
- fd, res, strerror(errno));
- this->consoleFd = -errno;
- return;
- }
-
- // switch to console 7 (which is what X normaly uses)
- int vtnum = 7;
- do {
- res = ioctl(fd, VT_ACTIVATE, (void*)vtnum);
- } while(res < 0 && errno == EINTR);
- if (res<0) {
- LOGE("ioctl(%d, VT_ACTIVATE, ...) failed, %d (%s) for %d",
- fd, errno, strerror(errno), vtnum);
- this->consoleFd = -errno;
- return;
- }
-
- do {
- res = ioctl(fd, VT_WAITACTIVE, (void*)vtnum);
- } while(res < 0 && errno == EINTR);
- if (res<0) {
- LOGE("ioctl(%d, VT_WAITACTIVE, ...) failed, %d %d %s for %d",
- fd, res, errno, strerror(errno), vtnum);
- this->consoleFd = -errno;
- return;
- }
-
- // open the new console
- close(fd);
- fd = open(ttydev, O_RDWR | O_SYNC);
- if (fd<0) {
- LOGE("Can't open new console %s", ttydev);
- this->consoleFd = -errno;
- return;
- }
-
- /* disable console line buffer, echo, ... */
- struct termios ttyarg;
- ioctl(fd, TCGETS , &ttyarg);
- ttyarg.c_iflag = 0;
- ttyarg.c_lflag = 0;
- ioctl(fd, TCSETS , &ttyarg);
-
- // set up signals so we're notified when the console changes
- // we can't use SIGUSR1 because it's used by the java-vm
- vm.mode = VT_PROCESS;
- vm.waitv = 0;
- vm.relsig = SIGUSR2;
- vm.acqsig = SIGUNUSED;
- vm.frsig = 0;
-
- struct sigaction act;
- sigemptyset(&act.sa_mask);
- act.sa_handler = sigHandler;
- act.sa_flags = 0;
- sigaction(vm.relsig, &act, NULL);
-
- sigemptyset(&act.sa_mask);
- act.sa_handler = sigHandler;
- act.sa_flags = 0;
- sigaction(vm.acqsig, &act, NULL);
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, vm.relsig);
- sigaddset(&mask, vm.acqsig);
- sigprocmask(SIG_BLOCK, &mask, NULL);
-
- // switch to graphic mode
- res = ioctl(fd, KDSETMODE, (void*)KD_GRAPHICS);
- LOGW_IF(res<0,
- "ioctl(%d, KDSETMODE, KD_GRAPHICS) failed, res %d", fd, res);
-
- this->prev_vt_num = vs.v_active;
- this->vt_num = vtnum;
- this->consoleFd = fd;
-}
-
-DisplayHardwareBase::ConsoleManagerThread::~ConsoleManagerThread()
-{
- if (this->consoleFd >= 0) {
- int fd = this->consoleFd;
- int prev_vt_num = this->prev_vt_num;
- int res;
- ioctl(fd, KDSETMODE, (void*)KD_TEXT);
- do {
- res = ioctl(fd, VT_ACTIVATE, (void*)prev_vt_num);
- } while(res < 0 && errno == EINTR);
- do {
- res = ioctl(fd, VT_WAITACTIVE, (void*)prev_vt_num);
- } while(res < 0 && errno == EINTR);
- close(fd);
- char const * const ttydev = "/dev/tty0";
- fd = open(ttydev, O_RDWR | O_SYNC);
- ioctl(fd, VT_DISALLOCATE, 0);
- close(fd);
- }
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::readyToRun()
-{
- if (this->consoleFd >= 0) {
- sSignalCatcherPid = gettid();
-
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, vm.relsig);
- sigaddset(&mask, vm.acqsig);
- sigprocmask(SIG_BLOCK, &mask, NULL);
-
- int res = ioctl(this->consoleFd, VT_SETMODE, &vm);
- if (res<0) {
- LOGE("ioctl(%d, VT_SETMODE, ...) failed, %d (%s)",
- this->consoleFd, errno, strerror(errno));
- }
- return NO_ERROR;
- }
- return this->consoleFd;
-}
-
-void DisplayHardwareBase::ConsoleManagerThread::requestExit()
-{
- Thread::requestExit();
- if (sSignalCatcherPid != 0) {
- // wake the thread up
- kill(sSignalCatcherPid, SIGINT);
- // wait for it...
- }
-}
-
-void DisplayHardwareBase::ConsoleManagerThread::sigHandler(int sig)
-{
- // resend the signal to our signal catcher thread
- LOGW("received signal %d in thread %d, resending to %d",
- sig, gettid(), sSignalCatcherPid);
-
- // we absolutely need the delays below because without them
- // our main thread never gets a chance to handle the signal.
- usleep(10000);
- kill(sSignalCatcherPid, sig);
- usleep(10000);
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::releaseScreen() const
-{
- int fd = this->consoleFd;
- int err = ioctl(fd, VT_RELDISP, (void*)1);
- LOGE_IF(err<0, "ioctl(%d, VT_RELDISP, 1) failed %d (%s)",
- fd, errno, strerror(errno));
- return (err<0) ? (-errno) : status_t(NO_ERROR);
-}
-
-bool DisplayHardwareBase::ConsoleManagerThread::threadLoop()
-{
- sigset_t mask;
- sigemptyset(&mask);
- sigaddset(&mask, vm.relsig);
- sigaddset(&mask, vm.acqsig);
-
- int sig = 0;
- sigwait(&mask, &sig);
-
- if (sig == vm.relsig) {
- sp<SurfaceFlinger> flinger = mFlinger.promote();
- //LOGD("About to give-up screen, flinger = %p", flinger.get());
- if (flinger != 0)
- flinger->screenReleased(0);
- } else if (sig == vm.acqsig) {
- sp<SurfaceFlinger> flinger = mFlinger.promote();
- //LOGD("Screen about to return, flinger = %p", flinger.get());
- if (flinger != 0)
- flinger->screenAcquired(0);
- }
-
- return true;
-}
-
-status_t DisplayHardwareBase::ConsoleManagerThread::initCheck() const
-{
- return consoleFd >= 0 ? NO_ERROR : NO_INIT;
+ return ((access(kSleepFileName, R_OK) == 0 &&
+ access(kWakeFileName, R_OK) == 0)) ? NO_ERROR : NO_INIT;
}
// ----------------------------------------------------------------------------
@@ -362,10 +125,6 @@
: mCanDraw(true), mScreenAcquired(true)
{
mDisplayEventThread = new DisplayEventThread(flinger);
- if (mDisplayEventThread->initCheck() != NO_ERROR) {
- // fall-back on the console
- mDisplayEventThread = new ConsoleManagerThread(flinger);
- }
}
DisplayHardwareBase::~DisplayHardwareBase()
diff --git a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
index fa6a0c4..30eb258 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayHardwareBase.h
@@ -73,24 +73,6 @@
virtual status_t initCheck() const;
};
- class ConsoleManagerThread : public DisplayEventThreadBase
- {
- int consoleFd;
- int vt_num;
- int prev_vt_num;
- vt_mode vm;
- static void sigHandler(int sig);
- static pid_t sSignalCatcherPid;
- public:
- ConsoleManagerThread(const sp<SurfaceFlinger>& flinger);
- virtual ~ConsoleManagerThread();
- virtual bool threadLoop();
- virtual status_t readyToRun();
- virtual void requestExit();
- virtual status_t releaseScreen() const;
- virtual status_t initCheck() const;
- };
-
sp<DisplayEventThreadBase> mDisplayEventThread;
mutable int mCanDraw;
mutable int mScreenAcquired;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
similarity index 77%
rename from services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
rename to services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 7b77aac..e8188e7 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -16,7 +16,7 @@
package com.android.server.pm;
-import com.android.server.pm.UserDetails;
+import com.android.server.pm.UserManager;
import android.content.pm.UserInfo;
import android.os.Debug;
@@ -25,23 +25,24 @@
import java.util.List;
-/** Test {@link UserDetails} functionality. */
-public class UserDetailsTest extends AndroidTestCase {
+/** Test {@link UserManager} functionality. */
+public class UserManagerTest extends AndroidTestCase {
- UserDetails mDetails = null;
+ UserManager mUserManager = null;
@Override
public void setUp() throws Exception {
- mDetails = new UserDetails(Environment.getExternalStorageDirectory());
+ mUserManager = new UserManager(Environment.getExternalStorageDirectory(),
+ Environment.getExternalStorageDirectory());
}
@Override
public void tearDown() throws Exception {
- List<UserInfo> users = mDetails.getUsers();
+ List<UserInfo> users = mUserManager.getUsers();
// Remove all except the primary user
for (UserInfo user : users) {
if (!user.isPrimary()) {
- mDetails.removeUser(user.id);
+ mUserManager.removeUser(user.id);
}
}
}
@@ -51,9 +52,9 @@
}
public void testAddUser() throws Exception {
- final UserDetails details = mDetails;
+ final UserManager details = mUserManager;
- UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
assertTrue(userInfo != null);
List<UserInfo> list = details.getUsers();
@@ -70,10 +71,10 @@
}
public void testAdd2Users() throws Exception {
- final UserDetails details = mDetails;
+ final UserManager details = mUserManager;
- UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
- UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
+ UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
+ UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN, null);
assertTrue(user1 != null);
assertTrue(user2 != null);
@@ -84,9 +85,9 @@
}
public void testRemoveUser() throws Exception {
- final UserDetails details = mDetails;
+ final UserManager details = mUserManager;
- UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST, null);
details.removeUser(userInfo.id);
@@ -94,7 +95,7 @@
}
private boolean findUser(int id) {
- List<UserInfo> list = mDetails.getUsers();
+ List<UserInfo> list = mUserManager.getUsers();
for (UserInfo user : list) {
if (user.id == id) {
diff --git a/telephony/java/com/android/internal/telephony/DataConnection.java b/telephony/java/com/android/internal/telephony/DataConnection.java
index 0c47e86..791fbfd 100644
--- a/telephony/java/com/android/internal/telephony/DataConnection.java
+++ b/telephony/java/com/android/internal/telephony/DataConnection.java
@@ -17,8 +17,8 @@
package com.android.internal.telephony;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import android.net.LinkAddress;
import android.net.LinkCapabilities;
@@ -37,7 +37,7 @@
/**
* {@hide}
*
- * DataConnection HierarchicalStateMachine.
+ * DataConnection StateMachine.
*
* This is an abstract base class for representing a single data connection.
* Instances of this class such as <code>CdmaDataConnection</code> and
@@ -55,7 +55,7 @@
*
* The other public methods are provided for debugging.
*/
-public abstract class DataConnection extends HierarchicalStateMachine {
+public abstract class DataConnection extends StateMachine {
protected static final boolean DBG = true;
protected static Object mCountLock = new Object();
@@ -484,17 +484,17 @@
/**
* The parent state for all other states.
*/
- private class DcDefaultState extends HierarchicalState {
+ private class DcDefaultState extends State {
@Override
- protected void enter() {
+ public void enter() {
phone.mCM.registerForRilConnected(getHandler(), EVENT_RIL_CONNECTED, null);
}
@Override
- protected void exit() {
+ public void exit() {
phone.mCM.unregisterForRilConnected(getHandler());
}
@Override
- protected boolean processMessage(Message msg) {
+ public boolean processMessage(Message msg) {
AsyncResult ar;
switch (msg.what) {
@@ -547,7 +547,7 @@
/**
* The state machine is inactive and expects a EVENT_CONNECT.
*/
- private class DcInactiveState extends HierarchicalState {
+ private class DcInactiveState extends State {
private ConnectionParams mConnectionParams = null;
private FailCause mFailCause = null;
private DisconnectParams mDisconnectParams = null;
@@ -563,7 +563,8 @@
mDisconnectParams = dp;
}
- @Override protected void enter() {
+ @Override
+ public void enter() {
mTag += 1;
/**
@@ -583,14 +584,16 @@
}
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
// clear notifications
mConnectionParams = null;
mFailCause = null;
mDisconnectParams = null;
}
- @Override protected boolean processMessage(Message msg) {
+ @Override
+ public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
@@ -626,8 +629,9 @@
/**
* The state machine is activating a connection.
*/
- private class DcActivatingState extends HierarchicalState {
- @Override protected boolean processMessage(Message msg) {
+ private class DcActivatingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
boolean retVal;
AsyncResult ar;
ConnectionParams cp;
@@ -722,7 +726,7 @@
/**
* The state machine is connected, expecting an EVENT_DISCONNECT.
*/
- private class DcActiveState extends HierarchicalState {
+ private class DcActiveState extends State {
private ConnectionParams mConnectionParams = null;
private FailCause mFailCause = null;
@@ -746,13 +750,15 @@
}
}
- @Override protected void exit() {
+ @Override
+ public void exit() {
// clear notifications
mConnectionParams = null;
mFailCause = null;
}
- @Override protected boolean processMessage(Message msg) {
+ @Override
+ public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
@@ -778,8 +784,9 @@
/**
* The state machine is disconnecting.
*/
- private class DcDisconnectingState extends HierarchicalState {
- @Override protected boolean processMessage(Message msg) {
+ private class DcDisconnectingState extends State {
+ @Override
+ public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
@@ -812,8 +819,9 @@
/**
* The state machine is disconnecting after an creating a connection.
*/
- private class DcDisconnectionErrorCreatingConnection extends HierarchicalState {
- @Override protected boolean processMessage(Message msg) {
+ private class DcDisconnectionErrorCreatingConnection extends State {
+ @Override
+ public boolean processMessage(Message msg) {
boolean retVal;
switch (msg.what) {
diff --git a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
index 3280c44..01ac95f 100644
--- a/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/DataConnectionTracker.java
@@ -365,14 +365,6 @@
return mActivity;
}
- /**
- * @return the data connections
- */
- public ArrayList<DataConnection> getAllDataConnections() {
- /** TODO: change return type to Collection? */
- return new ArrayList<DataConnection>(mDataConnections.values());
- }
-
public boolean isApnTypeActive(String type) {
// TODO: support simultaneous with List instead
if (Phone.APN_TYPE_DUN.equals(type)) {
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 488794f..b99f39b 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -159,8 +159,8 @@
static final String REASON_ROAMING_OFF = "roamingOff";
static final String REASON_DATA_DISABLED = "dataDisabled";
static final String REASON_DATA_ENABLED = "dataEnabled";
- static final String REASON_GPRS_ATTACHED = "gprsAttached";
- static final String REASON_GPRS_DETACHED = "gprsDetached";
+ static final String REASON_DATA_ATTACHED = "dataAttached";
+ static final String REASON_DATA_DETACHED = "dataDetached";
static final String REASON_CDMA_DATA_ATTACHED = "cdmaDataAttached";
static final String REASON_CDMA_DATA_DETACHED = "cdmaDataDetached";
static final String REASON_APN_CHANGED = "apnChanged";
@@ -1250,13 +1250,6 @@
void getDataCallList(Message response);
/**
- * Get current mutiple data connection status
- *
- * @return list of data connections
- */
- List<DataConnection> getCurrentDataConnectionList();
-
- /**
* Update the ServiceState CellLocation for current network registration.
*/
void updateServiceLocation();
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index 5e506b3..a3fc25d 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -593,10 +593,6 @@
mActivePhone.getDataCallList(response);
}
- public List<DataConnection> getCurrentDataConnectionList() {
- return mActivePhone.getCurrentDataConnectionList();
- }
-
public void updateServiceLocation() {
mActivePhone.updateServiceLocation();
}
diff --git a/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
index a197c9a..2a1f508 100644
--- a/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
+++ b/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java
@@ -20,15 +20,15 @@
import com.android.internal.telephony.IccUtils;
import android.os.Handler;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import android.os.Message;
/**
* Class used for queuing raw ril messages, decoding them into CommanParams
* objects and sending the result back to the CAT Service.
*/
-class RilMessageDecoder extends HierarchicalStateMachine {
+class RilMessageDecoder extends StateMachine {
// constants
private static final int CMD_START = 1;
@@ -101,8 +101,9 @@
mCmdParamsFactory = CommandParamsFactory.getInstance(this, fh);
}
- private class StateStart extends HierarchicalState {
- @Override protected boolean processMessage(Message msg) {
+ private class StateStart extends State {
+ @Override
+ public boolean processMessage(Message msg) {
if (msg.what == CMD_START) {
if (decodeMessageParams((RilMessage)msg.obj)) {
transitionTo(mStateCmdParamsReady);
@@ -115,8 +116,9 @@
}
}
- private class StateCmdParamsReady extends HierarchicalState {
- @Override protected boolean processMessage(Message msg) {
+ private class StateCmdParamsReady extends State {
+ @Override
+ public boolean processMessage(Message msg) {
if (msg.what == CMD_PARAMS_READY) {
mCurrentRilMessage.mResCode = ResultCode.fromInt(msg.arg1);
mCurrentRilMessage.mData = msg.obj;
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index b077a5e..152ffd5 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -727,10 +727,6 @@
return mDataConnectionTracker.getDataOnRoamingEnabled();
}
- public List<DataConnection> getCurrentDataConnectionList () {
- return mDataConnectionTracker.getAllDataConnections();
- }
-
public void setVoiceMailNumber(String alphaTag,
String voiceMailNumber,
Message onComplete) {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
index ff3e3cc..b137ca7 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java
@@ -1069,10 +1069,6 @@
mCM.getDataCallList(response);
}
- public List<DataConnection> getCurrentDataConnectionList () {
- return mDataConnectionTracker.getAllDataConnections();
- }
-
public void updateServiceLocation() {
mSST.enableSingleLocationUpdate();
}
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
index 9a91ee4..fc1d536 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java
@@ -505,9 +505,8 @@
ApnContext apnContext = mApnContexts.get(type);
if (apnContext != null) {
- apnContext.setPendingAction(ApnContext.PENDING_ACTION_APN_DISABLE);
-
if (apnContext.getState() != State.IDLE && apnContext.getState() != State.FAILED) {
+ apnContext.setPendingAction(ApnContext.PENDING_ACTION_APN_DISABLE);
Message msg = obtainMessage(EVENT_CLEAN_UP_CONNECTION);
msg.arg1 = 1; // tearDown is true;
// TODO - don't set things on apnContext from public functions.
@@ -519,6 +518,7 @@
return Phone.APN_REQUEST_STARTED;
} else {
if (DBG) log("return APN_ALREADY_INACTIVE");
+ apnContext.setEnabled(false);
return Phone.APN_ALREADY_INACTIVE;
}
@@ -582,26 +582,16 @@
* when GPRS detaches, but we should stop the network polling.
*/
stopNetStatPoll();
- notifyDataConnection(Phone.REASON_GPRS_DETACHED);
+ notifyDataConnection(Phone.REASON_DATA_DETACHED);
}
private void onDataConnectionAttached() {
if (getOverallState() == State.CONNECTED) {
startNetStatPoll();
- notifyDataConnection(Phone.REASON_GPRS_ATTACHED);
- } else {
- // Only check for default APN state
- ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
- if (defaultApnContext != null) {
- if (defaultApnContext.getState() == State.FAILED) {
- cleanUpConnection(false, defaultApnContext);
- if (defaultApnContext.getDataConnection() != null) {
- defaultApnContext.getDataConnection().resetRetryCount();
- }
- }
- trySetupData(Phone.REASON_GPRS_ATTACHED, Phone.APN_TYPE_DEFAULT);
- }
+ notifyDataConnection(Phone.REASON_DATA_ATTACHED);
}
+
+ setupDataOnReadyApns(Phone.REASON_DATA_ATTACHED);
}
@Override
@@ -610,7 +600,7 @@
boolean desiredPowerState = mPhone.getServiceStateTracker().getDesiredPowerState();
boolean allowed =
- (gprsState == ServiceState.STATE_IN_SERVICE || mAutoAttachOnCreation) &&
+ gprsState == ServiceState.STATE_IN_SERVICE &&
mPhone.mSIMRecords.getRecordsLoaded() &&
mPhone.getState() == Phone.State.IDLE &&
mInternalDataEnabled &&
@@ -619,7 +609,7 @@
desiredPowerState;
if (!allowed && DBG) {
String reason = "";
- if (!((gprsState == ServiceState.STATE_IN_SERVICE) || mAutoAttachOnCreation)) {
+ if (!(gprsState == ServiceState.STATE_IN_SERVICE)) {
reason += " - gprs= " + gprsState;
}
if (!mPhone.mSIMRecords.getRecordsLoaded()) reason += " - SIM not loaded";
@@ -637,6 +627,26 @@
return allowed;
}
+ private void setupDataOnReadyApns(String reason) {
+ // Only check for default APN state
+ for (ApnContext apnContext : mApnContexts.values()) {
+ if (apnContext.isReady()) {
+ if (apnContext.getState() == State.FAILED) {
+ cleanUpConnection(false, apnContext);
+ if (apnContext.getDataConnection() != null) {
+ apnContext.getDataConnection().resetRetryCount();
+ }
+ }
+ // Do not start ApnContext in SCANNING state
+ // FAILED state must be reset to IDLE by now
+ if (apnContext.getState() == State.IDLE) {
+ apnContext.setReason(reason);
+ trySetupData(apnContext);
+ }
+ }
+ }
+ }
+
private boolean trySetupData(String reason, String type) {
if (DBG) {
log("***trySetupData for type:" + type +
@@ -973,10 +983,7 @@
createAllApnList();
cleanUpAllConnections(isConnected, Phone.REASON_APN_CHANGED);
if (!isConnected) {
- // TODO: Won't work for multiple connections!!!!
- defaultApnContext.getDataConnection().resetRetryCount();
- defaultApnContext.setReason(Phone.REASON_APN_CHANGED);
- trySetupData(defaultApnContext);
+ setupDataOnReadyApns(Phone.REASON_APN_CHANGED);
}
}
@@ -1316,18 +1323,7 @@
private void onRecordsLoaded() {
if (DBG) log("onRecordsLoaded: createAllApnList");
createAllApnList();
- for (ApnContext apnContext : mApnContexts.values()) {
- if (apnContext.isReady()) {
- apnContext.setReason(Phone.REASON_SIM_LOADED);
- if (apnContext.getState() == State.FAILED) {
- if (DBG) {
- log("onRecordsLoaded clean connection for " + apnContext.getApnType());
- }
- cleanUpConnection(false, apnContext);
- }
- sendMessage(obtainMessage(EVENT_TRY_SETUP_DATA, apnContext));
- }
- }
+ setupDataOnReadyApns(Phone.REASON_SIM_LOADED);
}
@Override
@@ -1413,7 +1409,8 @@
@Override
// TODO: We shouldnt need this.
protected boolean onTrySetupData(String reason) {
- return trySetupData(reason, Phone.APN_TYPE_DEFAULT);
+ setupDataOnReadyApns(reason);
+ return true;
}
protected boolean onTrySetupData(ApnContext apnContext) {
@@ -1421,16 +1418,14 @@
}
@Override
- // TODO: Need to understand if more than DEFAULT is impacted?
protected void onRoamingOff() {
- trySetupData(Phone.REASON_ROAMING_OFF, Phone.APN_TYPE_DEFAULT);
+ setupDataOnReadyApns(Phone.REASON_ROAMING_OFF);
}
@Override
- // TODO: Need to understand if more than DEFAULT is impacted?
protected void onRoamingOn() {
if (getDataOnRoamingEnabled()) {
- trySetupData(Phone.REASON_ROAMING_ON, Phone.APN_TYPE_DEFAULT);
+ setupDataOnReadyApns(Phone.REASON_ROAMING_ON);
} else {
if (DBG) log("Tear down data connection on roaming.");
cleanUpAllConnections(true, Phone.REASON_ROAMING_ON);
@@ -1618,14 +1613,12 @@
}
}
- if (TextUtils.equals(apnContext.getApnType(), Phone.APN_TYPE_DEFAULT)
- && retryAfterDisconnected(apnContext.getReason())) {
+ // If APN is still enabled, try to bring it back up automatically
+ if (apnContext.isReady() && retryAfterDisconnected(apnContext.getReason())) {
SystemProperties.set("gsm.defaultpdpcontext.active", "false");
- trySetupData(apnContext);
- }
- else if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_RECONNECT)
- {
- apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
+ if (apnContext.getPendingAction() == ApnContext.PENDING_ACTION_RECONNECT) {
+ apnContext.setPendingAction(ApnContext.PENDING_ACTION_NONE);
+ }
trySetupData(apnContext);
}
}
@@ -1658,13 +1651,7 @@
}
} else {
// reset reconnect timer
- ApnContext defaultApnContext = mApnContexts.get(Phone.APN_TYPE_DEFAULT);
- if (defaultApnContext != null) {
- defaultApnContext.getDataConnection().resetRetryCount();
- mReregisterOnReconnectFailure = false;
- // in case data setup was attempted when we were on a voice call
- trySetupData(Phone.REASON_VOICE_CALL_ENDED, Phone.APN_TYPE_DEFAULT);
- }
+ setupDataOnReadyApns(Phone.REASON_VOICE_CALL_ENDED);
}
}
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 2afc935..c763b1d 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -388,6 +388,15 @@
</activity>
<activity
+ android:name="Lines2Activity"
+ android:label="_Lines2">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
+ <activity
android:name="PathsActivity"
android:label="_Paths">
<intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java
new file mode 100644
index 0000000..ccf0631
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/Lines2Activity.java
@@ -0,0 +1,149 @@
+/*
+ * 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.test.hwui;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class Lines2Activity extends Activity {
+ private ObjectAnimator mAnimator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().setBackgroundDrawable(new ColorDrawable(0xff000000));
+ FrameLayout frame = new FrameLayout(this);
+ final LinesView gpuView = new LinesView(this, 0, Color.GREEN);
+ frame.addView(gpuView);
+ final LinesView swView = new LinesView(this, 400, Color.RED);
+ swView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ frame.addView(swView);
+ final LinesView hwBothView = new LinesView(this, 850, Color.GREEN);
+ // BUG: some lines not drawn or drawn with alpha when enabling hw layers
+// hwBothView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ frame.addView(hwBothView);
+ final LinesView swBothView = new LinesView(this, 854, Color.RED);
+ swBothView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ frame.addView(swBothView);
+ setContentView(frame);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+
+ public static class LinesView extends View {
+
+ private float mOffset;
+ private int mColor;
+ private float[] basePoints = {
+ 120, 0, 120, 20, 120, 20, 125, 0, 130, 0, 132, 10
+ };
+ private float[] copyPoints = new float[12];
+
+ public LinesView(Context c, float offset, int color) {
+ super(c);
+ mOffset = offset;
+ mColor = color;
+ }
+
+ private void drawLines(Canvas canvas, Paint p, float xOffset, float yOffset) {
+ canvas.drawLine(10 + xOffset, yOffset, 10 + xOffset, 40 + yOffset, p);
+ canvas.drawLine(30 + xOffset, yOffset, 40 + xOffset, 40 + yOffset, p);
+ canvas.drawLine(40 + xOffset, yOffset, 75 + xOffset, 35 + yOffset, p);
+ canvas.drawLine(50 + xOffset, 5+ yOffset, 100 + xOffset, 15 + yOffset, p);
+ canvas.drawLine(60 + xOffset, yOffset, 110 + xOffset, 2 + yOffset, p);
+ canvas.drawLine(60 + xOffset, 40 + yOffset, 110 + xOffset, 40 + yOffset, p);
+ for (int i = 0; i < 12; i += 2) {
+ copyPoints[i] = basePoints[i] + xOffset;
+ copyPoints[i+1] = basePoints[i+1] + yOffset;
+ }
+ canvas.drawLines(copyPoints, 0, 12, p);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ Paint p = new Paint();
+ p.setColor(mColor);
+ float yOffset = 10;
+
+ canvas.save();
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+
+ yOffset +=100;
+ canvas.save();
+ p.setStrokeWidth(1);
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+
+ yOffset += 100;
+ canvas.save();
+ p.setStrokeWidth(2);
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+
+ p.setAntiAlias(true);
+ p.setStrokeWidth(0);
+ yOffset += 100;
+ canvas.save();
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+
+ yOffset += 100;
+ canvas.save();
+ p.setStrokeWidth(1);
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+
+ yOffset += 100;
+ canvas.save();
+ p.setStrokeWidth(2);
+ drawLines(canvas, p, mOffset, yOffset);
+ canvas.scale(2, 2);
+ canvas.translate(60, 0);
+ drawLines(canvas, p, mOffset/2, yOffset/2);
+ canvas.restore();
+ }
+ }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index 4233367..c3a91ce 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -53,7 +53,7 @@
}
public static class LinesView extends View {
- private static final boolean LINE_AA = false;
+ private static final boolean LINE_AA = true;
private final Bitmap mBitmap1;
private final Paint mSmallPaint;
diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java
index 3cde949..9ae26da 100644
--- a/wifi/java/android/net/wifi/SupplicantStateTracker.java
+++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java
@@ -16,8 +16,8 @@
package android.net.wifi;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import android.net.wifi.WifiStateMachine.StateChangeResult;
import android.content.Context;
@@ -33,7 +33,7 @@
* - detect a failed WPA handshake that loops indefinitely
* - authentication failure handling
*/
-class SupplicantStateTracker extends HierarchicalStateMachine {
+class SupplicantStateTracker extends StateMachine {
private static final String TAG = "SupplicantStateTracker";
private static final boolean DBG = false;
@@ -53,14 +53,14 @@
private Context mContext;
- private HierarchicalState mUninitializedState = new UninitializedState();
- private HierarchicalState mDefaultState = new DefaultState();
- private HierarchicalState mInactiveState = new InactiveState();
- private HierarchicalState mDisconnectState = new DisconnectedState();
- private HierarchicalState mScanState = new ScanState();
- private HierarchicalState mHandshakeState = new HandshakeState();
- private HierarchicalState mCompletedState = new CompletedState();
- private HierarchicalState mDormantState = new DormantState();
+ private State mUninitializedState = new UninitializedState();
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mDisconnectState = new DisconnectedState();
+ private State mScanState = new ScanState();
+ private State mHandshakeState = new HandshakeState();
+ private State mCompletedState = new CompletedState();
+ private State mDormantState = new DormantState();
public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) {
super(TAG, target.getLooper());
@@ -146,7 +146,7 @@
* HSM states
*******************************************************/
- class DefaultState extends HierarchicalState {
+ class DefaultState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -188,21 +188,21 @@
* or after we have lost the control channel
* connection to the supplicant
*/
- class UninitializedState extends HierarchicalState {
+ class UninitializedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
}
}
- class InactiveState extends HierarchicalState {
+ class InactiveState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
}
}
- class DisconnectedState extends HierarchicalState {
+ class DisconnectedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -221,14 +221,14 @@
}
}
- class ScanState extends HierarchicalState {
+ class ScanState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
}
}
- class HandshakeState extends HierarchicalState {
+ class HandshakeState extends State {
/**
* The max number of the WPA supplicant loop iterations before we
* decide that the loop should be terminated:
@@ -277,7 +277,7 @@
}
}
- class CompletedState extends HierarchicalState {
+ class CompletedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -318,7 +318,7 @@
}
//TODO: remove after getting rid of the state in supplicant
- class DormantState extends HierarchicalState {
+ class DormantState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 46c07a3..16611d8 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -73,8 +73,8 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import java.net.InetAddress;
import java.util.ArrayList;
@@ -88,7 +88,7 @@
*
* @hide
*/
-public class WifiStateMachine extends HierarchicalStateMachine {
+public class WifiStateMachine extends StateMachine {
private static final String TAG = "WifiStateMachine";
private static final String NETWORKTYPE = "WIFI";
@@ -358,50 +358,50 @@
private static final int MAX_RSSI = 256;
/* Default parent state */
- private HierarchicalState mDefaultState = new DefaultState();
+ private State mDefaultState = new DefaultState();
/* Temporary initial state */
- private HierarchicalState mInitialState = new InitialState();
+ private State mInitialState = new InitialState();
/* Unloading the driver */
- private HierarchicalState mDriverUnloadingState = new DriverUnloadingState();
+ private State mDriverUnloadingState = new DriverUnloadingState();
/* Loading the driver */
- private HierarchicalState mDriverUnloadedState = new DriverUnloadedState();
+ private State mDriverUnloadedState = new DriverUnloadedState();
/* Driver load/unload failed */
- private HierarchicalState mDriverFailedState = new DriverFailedState();
+ private State mDriverFailedState = new DriverFailedState();
/* Driver loading */
- private HierarchicalState mDriverLoadingState = new DriverLoadingState();
+ private State mDriverLoadingState = new DriverLoadingState();
/* Driver loaded */
- private HierarchicalState mDriverLoadedState = new DriverLoadedState();
+ private State mDriverLoadedState = new DriverLoadedState();
/* Driver loaded, waiting for supplicant to start */
- private HierarchicalState mSupplicantStartingState = new SupplicantStartingState();
+ private State mSupplicantStartingState = new SupplicantStartingState();
/* Driver loaded and supplicant ready */
- private HierarchicalState mSupplicantStartedState = new SupplicantStartedState();
+ private State mSupplicantStartedState = new SupplicantStartedState();
/* Waiting for supplicant to stop and monitor to exit */
- private HierarchicalState mSupplicantStoppingState = new SupplicantStoppingState();
+ private State mSupplicantStoppingState = new SupplicantStoppingState();
/* Driver start issued, waiting for completed event */
- private HierarchicalState mDriverStartingState = new DriverStartingState();
+ private State mDriverStartingState = new DriverStartingState();
/* Driver started */
- private HierarchicalState mDriverStartedState = new DriverStartedState();
+ private State mDriverStartedState = new DriverStartedState();
/* Driver stopping */
- private HierarchicalState mDriverStoppingState = new DriverStoppingState();
+ private State mDriverStoppingState = new DriverStoppingState();
/* Driver stopped */
- private HierarchicalState mDriverStoppedState = new DriverStoppedState();
+ private State mDriverStoppedState = new DriverStoppedState();
/* Scan for networks, no connection will be established */
- private HierarchicalState mScanModeState = new ScanModeState();
+ private State mScanModeState = new ScanModeState();
/* Connecting to an access point */
- private HierarchicalState mConnectModeState = new ConnectModeState();
+ private State mConnectModeState = new ConnectModeState();
/* Fetching IP after network connection (assoc+auth complete) */
- private HierarchicalState mConnectingState = new ConnectingState();
+ private State mConnectingState = new ConnectingState();
/* Connected with IP addr */
- private HierarchicalState mConnectedState = new ConnectedState();
+ private State mConnectedState = new ConnectedState();
/* disconnect issued, waiting for network disconnect confirmation */
- private HierarchicalState mDisconnectingState = new DisconnectingState();
+ private State mDisconnectingState = new DisconnectingState();
/* Network is not connected, supplicant assoc+auth is not complete */
- private HierarchicalState mDisconnectedState = new DisconnectedState();
+ private State mDisconnectedState = new DisconnectedState();
/* Waiting for WPS to be completed*/
- private HierarchicalState mWaitForWpsCompletionState = new WaitForWpsCompletionState();
+ private State mWaitForWpsCompletionState = new WaitForWpsCompletionState();
/* Soft Ap is running */
- private HierarchicalState mSoftApStartedState = new SoftApStartedState();
+ private State mSoftApStartedState = new SoftApStartedState();
/**
@@ -1543,7 +1543,7 @@
* HSM states
*******************************************************/
- class DefaultState extends HierarchicalState {
+ class DefaultState extends State {
@Override
public boolean processMessage(Message message) {
if (DBG) Log.d(TAG, getName() + message.toString() + "\n");
@@ -1617,7 +1617,7 @@
}
}
- class InitialState extends HierarchicalState {
+ class InitialState extends State {
@Override
//TODO: could move logging into a common class
public void enter() {
@@ -1636,7 +1636,7 @@
}
}
- class DriverLoadingState extends HierarchicalState {
+ class DriverLoadingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1715,7 +1715,7 @@
}
}
- class DriverLoadedState extends HierarchicalState {
+ class DriverLoadedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1768,7 +1768,7 @@
}
}
- class DriverUnloadingState extends HierarchicalState {
+ class DriverUnloadingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1849,7 +1849,7 @@
}
}
- class DriverUnloadedState extends HierarchicalState {
+ class DriverUnloadedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1870,7 +1870,7 @@
}
}
- class DriverFailedState extends HierarchicalState {
+ class DriverFailedState extends State {
@Override
public void enter() {
Log.e(TAG, getName() + "\n");
@@ -1884,7 +1884,7 @@
}
- class SupplicantStartingState extends HierarchicalState {
+ class SupplicantStartingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1956,7 +1956,7 @@
}
}
- class SupplicantStartedState extends HierarchicalState {
+ class SupplicantStartedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -1978,17 +1978,6 @@
boolean eventLoggingEnabled = true;
switch(message.what) {
case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */
- Log.d(TAG, "stopping supplicant");
- if (!WifiNative.stopSupplicant()) {
- Log.e(TAG, "Failed to stop supplicant, issue kill");
- WifiNative.killSupplicant();
- }
- mNetworkInfo.setIsAvailable(false);
- handleNetworkDisconnect();
- setWifiState(WIFI_STATE_DISABLING);
- sendSupplicantConnectionChangedBroadcast(false);
- mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
- mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE);
transitionTo(mSupplicantStoppingState);
break;
case SUP_DISCONNECTION_EVENT: /* Supplicant connection lost */
@@ -2084,11 +2073,22 @@
}
}
- class SupplicantStoppingState extends HierarchicalState {
+ class SupplicantStoppingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName());
+ Log.d(TAG, "stopping supplicant");
+ if (!WifiNative.stopSupplicant()) {
+ Log.e(TAG, "Failed to stop supplicant, issue kill");
+ WifiNative.killSupplicant();
+ }
+ mNetworkInfo.setIsAvailable(false);
+ handleNetworkDisconnect();
+ setWifiState(WIFI_STATE_DISABLING);
+ sendSupplicantConnectionChangedBroadcast(false);
+ mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
+ mWpsStateMachine.sendMessage(CMD_RESET_WPS_STATE);
}
@Override
public boolean processMessage(Message message) {
@@ -2127,7 +2127,7 @@
}
}
- class DriverStartingState extends HierarchicalState {
+ class DriverStartingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2168,7 +2168,7 @@
}
}
- class DriverStartedState extends HierarchicalState {
+ class DriverStartedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2272,7 +2272,7 @@
}
}
- class DriverStoppingState extends HierarchicalState {
+ class DriverStoppingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2308,7 +2308,7 @@
}
}
- class DriverStoppedState extends HierarchicalState {
+ class DriverStoppedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2332,7 +2332,7 @@
}
}
- class ScanModeState extends HierarchicalState {
+ class ScanModeState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2369,7 +2369,7 @@
}
}
- class ConnectModeState extends HierarchicalState {
+ class ConnectModeState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2479,7 +2479,7 @@
}
}
- class ConnectingState extends HierarchicalState {
+ class ConnectingState extends State {
boolean mModifiedBluetoothCoexistenceMode;
int mPowerMode;
boolean mUseStaticIp;
@@ -2677,7 +2677,7 @@
}
}
- class ConnectedState extends HierarchicalState {
+ class ConnectedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2789,7 +2789,7 @@
}
}
- class DisconnectingState extends HierarchicalState {
+ class DisconnectingState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2819,7 +2819,7 @@
}
}
- class DisconnectedState extends HierarchicalState {
+ class DisconnectedState extends State {
private boolean mAlarmEnabled = false;
/* This is set from the overlay config file or from a secure setting.
* A value of 0 disables scanning in the framework.
@@ -2931,7 +2931,7 @@
}
}
- class WaitForWpsCompletionState extends HierarchicalState {
+ class WaitForWpsCompletionState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -2970,7 +2970,7 @@
}
}
- class SoftApStartedState extends HierarchicalState {
+ class SoftApStartedState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
diff --git a/wifi/java/android/net/wifi/WpsStateMachine.java b/wifi/java/android/net/wifi/WpsStateMachine.java
index 32d77a1..120b228 100644
--- a/wifi/java/android/net/wifi/WpsStateMachine.java
+++ b/wifi/java/android/net/wifi/WpsStateMachine.java
@@ -17,8 +17,8 @@
package android.net.wifi;
import com.android.internal.util.AsyncChannel;
-import com.android.internal.util.HierarchicalState;
-import com.android.internal.util.HierarchicalStateMachine;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
import android.content.Context;
import android.content.Intent;
@@ -46,7 +46,7 @@
* reloads the configuration and updates the IP and proxy
* settings, if any.
*/
-class WpsStateMachine extends HierarchicalStateMachine {
+class WpsStateMachine extends StateMachine {
private static final String TAG = "WpsStateMachine";
private static final boolean DBG = false;
@@ -58,9 +58,9 @@
private Context mContext;
AsyncChannel mReplyChannel = new AsyncChannel();
- private HierarchicalState mDefaultState = new DefaultState();
- private HierarchicalState mInactiveState = new InactiveState();
- private HierarchicalState mActiveState = new ActiveState();
+ private State mDefaultState = new DefaultState();
+ private State mInactiveState = new InactiveState();
+ private State mActiveState = new ActiveState();
public WpsStateMachine(Context context, WifiStateMachine wsm, Handler target) {
super(TAG, target.getLooper());
@@ -82,7 +82,7 @@
* HSM states
*******************************************************/
- class DefaultState extends HierarchicalState {
+ class DefaultState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -128,7 +128,7 @@
}
}
- class ActiveState extends HierarchicalState {
+ class ActiveState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");
@@ -182,7 +182,7 @@
}
}
- class InactiveState extends HierarchicalState {
+ class InactiveState extends State {
@Override
public void enter() {
if (DBG) Log.d(TAG, getName() + "\n");