diff options
107 files changed, 5435 insertions, 1801 deletions
diff --git a/api/current.txt b/api/current.txt index 374c415f5631..975444ef44aa 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2851,6 +2851,7 @@ package android.app { 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); diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 80ba1e986dca..9aa70a458240 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -48,6 +48,11 @@ int install(const char *pkgname, uid_t uid, gid_t gid) 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 @@ int install(const char *pkgname, uid_t uid, gid_t gid) 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 @@ int install(const char *pkgname, uid_t uid, gid_t gid) 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 @@ int renamepkg(const char *oldpkgname, const char *newpkgname) 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 e0d0f97b3094..c062d36ec93b 100644 --- a/cmds/installd/installd.c +++ b/cmds/installd/installd.c @@ -49,7 +49,7 @@ static int do_rm_dex(char **arg, char reply[REPLY_MAX]) 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_get_size(char **arg, char reply[REPLY_MAX]) 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 @@ struct cmdinfo cmds[] = { { "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 @@ int initialize_globals() { 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 @@ int main(const int argc, const char *argv[]) { 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 cbca135c3043..e5f6739d9081 100644 --- a/cmds/installd/installd.h +++ b/cmds/installd/installd.h @@ -102,6 +102,9 @@ int create_pkg_path(char path[PKG_PATH_MAX], 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 validate_apk_path(const char *path); 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 f37a6fbae152..3099b8341f6e 100644 --- a/cmds/installd/utils.c +++ b/cmds/installd/utils.c @@ -96,6 +96,46 @@ int create_pkg_path(char path[PKG_PATH_MAX], } /** + * 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 @@ int append_and_increment(char** dst, const char* src, size_t* dst_size) { *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 d058e38b792d..78a450ca5d7e 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.pm.PermissionInfo; 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 @@ public final class Pm { 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 @@ public final class Pm { 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 final class Pm { } } + 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 @@ public final class Pm { 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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ef8ba8e7ea85..85918cf7670e 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1113,7 +1113,11 @@ final class ApplicationPackageManager extends PackageManager { */ @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 @@ final class ApplicationPackageManager extends PackageManager { */ @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 a528a1aae08e..dd158f94fbab 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -326,8 +326,9 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener 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 +960,19 @@ public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener } /** + * 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 1eaca3be6596..0da656fb6419 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -714,13 +714,14 @@ final class FragmentManagerImpl extends FragmentManager { 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 @@ final class FragmentManagerImpl extends FragmentManager { 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 @@ final class FragmentManagerImpl extends FragmentManager { 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 @@ final class FragmentManagerImpl extends FragmentManager { + " 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 @@ final class FragmentManagerImpl extends FragmentManager { } public void dispatchStop() { - moveToState(Fragment.ACTIVITY_CREATED, false); + moveToState(Fragment.STOPPED, false); } public void dispatchDestroy() { diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java index 6e2f4b6b54d3..a5ee26c9a22f 100644 --- a/core/java/android/app/ListFragment.java +++ b/core/java/android/app/ListFragment.java @@ -195,11 +195,11 @@ public class ListFragment extends Fragment { } /** - * 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/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java index f4693c21c01d..56f236d164b9 100644 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -26,8 +26,8 @@ import android.server.BluetoothA2dpService; 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 @@ import java.util.Set; * 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } } - 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 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } @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 18060a0234d5..98afdb82a8e6 100644 --- a/core/java/android/bluetooth/BluetoothProfileState.java +++ b/core/java/android/bluetooth/BluetoothProfileState.java @@ -22,8 +22,8 @@ import android.content.IntentFilter; 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 @@ import com.android.internal.util.HierarchicalStateMachine; * @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 @@ public class BluetoothProfileState extends HierarchicalStateMachine { 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 @@ public class BluetoothProfileState extends HierarchicalStateMachine { } } - 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/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fbf8f9225291..11cd44667154 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.PermissionGroupInfo; 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 @@ interface IPackageManager { 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 99c4c7f86eb8..ff817c1bb1d4 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -662,10 +662,15 @@ public abstract class PackageManager { 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 @@ public abstract class PackageManager { * @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 54a8842010cb..564f4f4a4618 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.Configuration; 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 000000000000..2e7cb8ffa0db --- /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 3704d3ab30b0..ba5331c77487 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -74,8 +74,7 @@ public class UserInfo implements Parcelable { @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/speech/tts/FileSynthesisRequest.java b/core/java/android/speech/tts/FileSynthesisRequest.java index 6a9b2dc14de3..7efc26466c8e 100644 --- a/core/java/android/speech/tts/FileSynthesisRequest.java +++ b/core/java/android/speech/tts/FileSynthesisRequest.java @@ -45,6 +45,7 @@ class FileSynthesisRequest extends SynthesisRequest { 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 @@ class FileSynthesisRequest extends SynthesisRequest { } @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 @@ class FileSynthesisRequest extends SynthesisRequest { 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 @@ class FileSynthesisRequest extends SynthesisRequest { } @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 @@ class FileSynthesisRequest extends SynthesisRequest { 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 226701518805..dc5ff708efcd 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisRequest.java +++ b/core/java/android/speech/tts/PlaybackSynthesisRequest.java @@ -50,6 +50,7 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { = 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 @@ class PlaybackSynthesisRequest extends SynthesisRequest { 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 f4bb85228901..515218b2a38e 100644 --- a/core/java/android/speech/tts/SynthesisRequest.java +++ b/core/java/android/speech/tts/SynthesisRequest.java @@ -114,6 +114,11 @@ public abstract class SynthesisRequest { 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 class SynthesisRequest { 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 a408ea2e9665..da97fb4bdf80 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -150,12 +150,10 @@ public abstract class TextToSpeechService extends Service { * * 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 @@ public abstract class TextToSpeechService extends Service { 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/services/input/SpotController.cpp b/core/java/android/view/PointerIcon.aidl index dffad81009c7..b09340b41e09 100644 --- a/services/input/SpotController.cpp +++ b/core/java/android/view/PointerIcon.aidl @@ -14,32 +14,6 @@ * limitations under the License. */ -#define LOG_TAG "SpotController" +package android.view; -//#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 +parcelable PointerIcon; diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java new file mode 100644 index 000000000000..bb7ed41a05fc --- /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/widget/ListView.java b/core/java/android/widget/ListView.java index af954c9022e6..9d29a608f1d3 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -251,7 +251,7 @@ public class ListView extends AbsListView { */ 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 @@ public class ListView extends AbsListView { 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 @@ public class ListView extends AbsListView { 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 @@ public class ListView extends AbsListView { * @param isSelectable true if the footer view can be selected */ public void addFooterView(View v, Object data, boolean isSelectable) { + + if (mAdapter != null && ! (mAdapter instanceof HeaderViewListAdapter)) { + throw new IllegalStateException( + "Cannot add footer view to list -- setAdapter has already been called."); + } + FixedViewInfo info = new FixedViewInfo(); info.view = v; info.data = data; @@ -371,7 +385,9 @@ public class ListView extends AbsListView { 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/util/HierarchicalState.java b/core/java/com/android/internal/util/IState.java index b37f46c1fe43..056f8e9b0424 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 @@ import android.os.Message; /** * {@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 @@ public class HierarchicalState { * 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. - */ - protected boolean processMessage(Message msg) { - return false; - } - - /** - * Called when a state is exited. + * @return HANDLED if processing has completed and NOT_HANDLED + * if the message wasn't processed. */ - protected void exit() { - } + boolean processMessage(Message msg); /** - * @return name of state, but default returns the states - * class name. An instance name would be better but requiring - * it seems unnecessary. + * Name of State for debugging purposes. + * + * @return name of state. */ - 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 000000000000..3eadff58bc09 --- /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 index f6aa1847d6af..cbe72dd2ada4 100644 --- a/core/java/com/android/internal/util/HierarchicalStateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -29,10 +29,10 @@ import java.util.Vector; /** * {@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 @@ import java.util.Vector; * 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 @@ import java.util.Vector; * 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 @@ class HelloWorld extends HierarchicalStateMachine { 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 @@ state mP2 { } } </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 @@ class Hsm1 extends HierarchicalStateMachine { 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 Hsm1 extends HierarchicalStateMachine { } } - class S1 extends HierarchicalState { + class S1 extends State { @Override public void enter() { Log.d(TAG, "mS1.enter"); } @@ -302,7 +302,7 @@ class Hsm1 extends HierarchicalStateMachine { } } - class S2 extends HierarchicalState { + class S2 extends State { @Override public void enter() { Log.d(TAG, "mS2.enter"); } @@ -330,7 +330,7 @@ class Hsm1 extends HierarchicalStateMachine { } } - 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): mP2.exit 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 class HierarchicalStateMachine { */ public static class ProcessedMessageInfo { private int what; - private HierarchicalState state; - private HierarchicalState orgState; + private State state; + private State orgState; /** * Constructor @@ -451,7 +451,7 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { /** * @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 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { } } - private static class HsmHandler extends Handler { + private static class SmHandler extends Handler { /** The debug flag */ private boolean mDbg = false; @@ -643,8 +643,8 @@ public class HierarchicalStateMachine { /** 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 @@ public class HierarchicalStateMachine { */ 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 @@ public class HierarchicalStateMachine { } /** 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 @@ public class HierarchicalStateMachine { /** * 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 @@ public class HierarchicalStateMachine { /** * 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 @@ public class HierarchicalStateMachine { * 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 @@ public class HierarchicalStateMachine { /** * 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 @@ public class HierarchicalStateMachine { * state. All subsequent messages will be processed in * in the halting state which invokes haltedProcessMessage(msg); */ - mHsm.halting(); + mSm.halting(); } } } @@ -833,7 +833,7 @@ public class HierarchicalStateMachine { * starting at the first entry. */ mIsConstructionCompleted = true; - mMsg = obtainMessage(HSM_INIT_CMD); + mMsg = obtainMessage(SM_INIT_CMD); invokeEnterMethods(0); /** @@ -863,7 +863,7 @@ public class HierarchicalStateMachine { /** * No parents left so it's not handled */ - mHsm.unhandledMessage(msg); + mSm.unhandledMessage(msg); if (isQuit(msg)) { transitionTo(mQuittingState); } @@ -878,7 +878,7 @@ public class HierarchicalStateMachine { * 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 @@ public class HierarchicalStateMachine { 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 @@ public class HierarchicalStateMachine { * 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 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { /** * @return current state */ - private final HierarchicalState getCurrentState() { + private final IState getCurrentState() { return mStateStack[mStateStackTopIndex].state; } @@ -1032,7 +1032,7 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { * 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 @@ public class HierarchicalStateMachine { 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 @@ public class HierarchicalStateMachine { */ 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 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { * * @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 @@ public class HierarchicalStateMachine { * * @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 @@ public class HierarchicalStateMachine { * will be called. */ protected final void transitionToHaltingState() { - mHsmHandler.transitionTo(mHsmHandler.mHaltingState); + mSmHandler.transitionTo(mSmHandler.mHaltingState); } /** @@ -1253,7 +1253,7 @@ public class HierarchicalStateMachine { * @param msg is deferred until the next transition. */ protected final void deferMessage(Message msg) { - mHsmHandler.deferMessage(msg); + mSmHandler.deferMessage(msg); } @@ -1263,7 +1263,7 @@ public class HierarchicalStateMachine { * @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 @@ public class HierarchicalStateMachine { /** * 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 @@ public class HierarchicalStateMachine { * @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 class HierarchicalStateMachine { */ public final Message obtainMessage() { - return Message.obtain(mHsmHandler); + return Message.obtain(mSmHandler); } /** @@ -1351,7 +1351,7 @@ public class HierarchicalStateMachine { * @return message */ public final Message obtainMessage(int what) { - return Message.obtain(mHsmHandler, what); + return Message.obtain(mSmHandler, what); } /** @@ -1364,7 +1364,7 @@ public class HierarchicalStateMachine { */ 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 class HierarchicalStateMachine { */ 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 class HierarchicalStateMachine { */ 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 @@ public class HierarchicalStateMachine { * @param dbg is true to enable debugging. */ public void setDbg(boolean dbg) { - mHsmHandler.setDbg(dbg); + mSmHandler.setDbg(dbg); } /** @@ -1510,6 +1510,6 @@ public class HierarchicalStateMachine { */ public void start() { /** Send the complete construction message */ - mHsmHandler.completeConstruction(); + mSmHandler.completeConstruction(); } } diff --git a/core/jni/Android.mk b/core/jni/Android.mk index f8f8761e1b95..290f5283a7a4 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -54,6 +54,7 @@ LOCAL_SRC_FILES:= \ 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 b628b9dc8caf..a4a229a84359 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -170,6 +170,7 @@ extern int register_android_view_InputChannel(JNIEnv* env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 207c72fa173e..310f02f97169 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -743,7 +743,11 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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/RtlProperties.h b/core/jni/android/graphics/RtlProperties.h index 4fac89a6fabd..a41c91b0812a 100644 --- a/core/jni/android/graphics/RtlProperties.h +++ b/core/jni/android/graphics/RtlProperties.h @@ -52,7 +52,7 @@ static RtlDebugLevel readRtlDebugLevel() { #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/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp index 088202e277e7..77a731a3e948 100644 --- a/core/jni/android/graphics/TextLayoutCache.cpp +++ b/core/jni/android/graphics/TextLayoutCache.cpp @@ -420,7 +420,9 @@ void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar 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 @@ void TextLayoutCacheValue::computeValuesWithHarfbuzz(SkPaint* paint, const UChar #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 @@ void TextLayoutCacheValue::computeRunValuesWithHarfbuzz(SkPaint* paint, const UC #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 000000000000..091341a3c3fc --- /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 000000000000..3bfd64526ad6 --- /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 Binary files differnew file mode 100644 index 000000000000..d7aca3677085 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_spot_anchor.png 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 000000000000..2222b8e023e2 --- /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 Binary files differnew file mode 100644 index 000000000000..5041aa3a022f --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_spot_hover.png 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 000000000000..dc62a696eb3b --- /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 Binary files differnew file mode 100644 index 000000000000..64a42a18f2c3 --- /dev/null +++ b/core/res/res/drawable-mdpi/pointer_spot_touch.png 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 000000000000..4bffee6ab58b --- /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/values/attrs.xml b/core/res/res/values/attrs.xml index 819ce583f208..e8767d80a82c 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 bf4c6d734e8e..b4042c059804 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -2210,4 +2210,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 b1e4f0f28875..0748b1075838 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 --> diff --git a/core/tests/coretests/src/com/android/internal/util/HierarchicalStateMachineTest.java b/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java index b6f8be577e41..ab6b2b6f1fbd 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.Looper; 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 android.util.Log; 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { // 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 @@ public class HierarchicalStateMachineTest extends TestCase { /** * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } } - 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 @@ public class HierarchicalStateMachineTest extends TestCase { /** * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } return HANDLED; } - - @Override protected void exit() { - mExitCount++; - } } @Override @@ -493,7 +505,7 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } } - 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 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } } - 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 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { transitionTo(mChildState3); return HANDLED; } - @Override protected void exit() { - mChildState5ExitCount += 1; - } } @Override @@ -1089,7 +1129,7 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } return HANDLED; } - - @Override protected void exit() { - } } @Override @@ -1178,7 +1215,7 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } return HANDLED; } - - @Override protected void exit() { - } } @Override @@ -1286,7 +1322,7 @@ public class HierarchicalStateMachineTest extends TestCase { /** * Test unhandledMessage. */ - class StateMachineUnhandledMessage extends HierarchicalStateMachine { + class StateMachineUnhandledMessage extends StateMachine { StateMachineUnhandledMessage(String name) { super(name); mThisSm = this; @@ -1298,13 +1334,14 @@ public class HierarchicalStateMachineTest extends TestCase { // 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 @@ public class HierarchicalStateMachineTest extends TestCase { * 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 @@ public class HierarchicalStateMachineTest extends TestCase { 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 @@ public class HierarchicalStateMachineTest extends TestCase { } } -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 @@ class Hsm1 extends HierarchicalStateMachine { 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 @@ class Hsm1 extends HierarchicalStateMachine { } 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 @@ class Hsm1 extends HierarchicalStateMachine { 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 @@ class Hsm1 extends HierarchicalStateMachine { } 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 @@ class Hsm1 extends HierarchicalStateMachine { } return HANDLED; } - @Override protected void exit() { - Log.d(TAG, "P2.exit"); - } } @Override diff --git a/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 340daaf14efd..96828c640c27 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -127,11 +127,28 @@ public: // 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 @@ private: // 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 @@ private: // 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 df82bf217f72..fe9b04917ed8 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 @@ public: sp<ISurfaceTexture> getISurfaceTexture() const; private: + friend class Surface; // can't be copied SurfaceTextureClient& operator = (const SurfaceTextureClient& rhs); @@ -78,6 +81,8 @@ private: 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 @@ private: // 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 0dc29c8e0b98..9b92c73c4274 100644 --- a/include/ui/Input.h +++ b/include/ui/Input.h @@ -620,6 +620,11 @@ private: // 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/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index e2346f060ecf..39418f03adae 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 @@ SurfaceTexture::SurfaceTexture(GLuint tex) : 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 @@ status_t SurfaceTexture::dequeueBuffer(int *buf) { if (buffer == NULL) { return ISurfaceTexture::BUFFER_NEEDS_REALLOCATION; } + if ((mUseDefaultSize) && ((uint32_t(buffer->width) != mDefaultWidth) || (uint32_t(buffer->height) != mDefaultHeight))) { @@ -263,9 +267,6 @@ status_t SurfaceTexture::updateTexImage() { 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 @@ status_t SurfaceTexture::updateTexImage() { 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 @@ status_t SurfaceTexture::updateTexImage() { // 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 @@ EGLImageKHR SurfaceTexture::createImage(EGLDisplay dpy, 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 29fc4d3edc1e..f4b24162f0bf 100644 --- a/libs/gui/SurfaceTextureClient.cpp +++ b/libs/gui/SurfaceTextureClient.cpp @@ -26,8 +26,10 @@ namespace android { 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 @@ int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t** buffer) { } 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 @@ int SurfaceTextureClient::dequeueBuffer(android_native_buffer_t** buffer) { 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 @@ int SurfaceTextureClient::query(int what, int* value) { 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::dispatchSetBuffersTimestamp(va_list args) { 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/ui/Input.cpp b/libs/ui/Input.cpp index bbe579ea83f2..a95f4326193b 100644 --- a/libs/ui/Input.cpp +++ b/libs/ui/Input.cpp @@ -832,6 +832,7 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const 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 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const // 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 d5dd126065e7..b54fb9dd7353 100644 --- a/libs/utils/Looper.cpp +++ b/libs/utils/Looper.cpp @@ -662,7 +662,8 @@ void Looper::wakeAndLock() { #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/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 1ca2d6d73355..f9db1a1bdec6 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -377,7 +377,7 @@ status_t MPEG4Extractor::readMetaData() { mFileMetaData->setCString(kKeyMIMEType, "audio/mp4"); } - mInitCheck = verifyIfStreamable(); + mInitCheck = OK; } else { mInitCheck = err; } @@ -1904,7 +1904,7 @@ status_t MPEG4Source::read( 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 MPEG4Source::read( status_t err = mSampleTable->getMetaDataForSample( - mCurrentSampleIndex, &offset, &size, &dts, &isSyncSample); + mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample); if (err != OK) { return err; @@ -1942,7 +1942,7 @@ status_t MPEG4Source::read( 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 @@ status_t MPEG4Source::read( 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 @@ MPEG4Extractor::Track *MPEG4Extractor::findTrackByMimePrefix( 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 c1aa46edb3e6..dc868852568a 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -323,25 +323,28 @@ void NuCachedSource2::onRead(const sp<AMessage> &msg) { } 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 @@ size_t NuCachedSource2::approxDataRemaining_l(status_t *finalStatus) { } 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 423df705a592..08db9021314e 100644 --- a/media/libstagefright/SampleTable.cpp +++ b/media/libstagefright/SampleTable.cpp @@ -53,6 +53,7 @@ SampleTable::SampleTable(const sp<DataSource> &source) mNumSampleSizes(0), mTimeToSampleCount(0), mTimeToSample(NULL), + mSampleTimeEntries(NULL), mCompositionTimeDeltaEntries(NULL), mNumCompositionTimeDeltaEntries(0), mSyncSampleOffset(-1), @@ -73,6 +74,9 @@ SampleTable::~SampleTable() { delete[] mCompositionTimeDeltaEntries; mCompositionTimeDeltaEntries = NULL; + delete[] mSampleTimeEntries; + mSampleTimeEntries = NULL; + delete[] mTimeToSample; mTimeToSample = NULL; @@ -381,67 +385,128 @@ uint32_t abs_difference(uint32_t time1, uint32_t time2) { 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; - - uint32_t time1 = time + j * delta; - uint32_t time2 = time1 + delta; - - 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; - } + for (uint32_t j = 0; j < n; ++j) { + CHECK(sampleIndex < mNumSampleSizes); - switch (flags) { - case kFlagBefore: - { - if (sampleTime > req_time && *sample_index > 0) { - --*sample_index; - } - break; - } + mSampleTimeEntries[sampleIndex].mSampleIndex = sampleIndex; - case kFlagAfter: - { - if (sampleTime < req_time - && *sample_index + 1 < mNumSampleSizes) { - ++*sample_index; - } - break; - } + mSampleTimeEntries[sampleIndex].mCompositionTime = + sampleTime + getCompositionTimeOffset(sampleIndex); + + ++sampleIndex; + sampleTime += delta; + } + } + + qsort(mSampleTimeEntries, mNumSampleSizes, sizeof(SampleTimeEntry), + CompareIncreasingTime); +} - default: - break; +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; + } - return OK; + case kFlagAfter: + { + while (closestIndex + 1 < mNumSampleSizes + && mSampleTimeEntries[closestIndex].mCompositionTime + < req_time) { + ++closestIndex; + } + break; } - time += delta * n; - cur_sample += n; + 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; + } } - return ERROR_OUT_OF_RANGE; + *sample_index = mSampleTimeEntries[closestIndex].mSampleIndex; + + return OK; } status_t SampleTable::findSyncSampleNear( @@ -613,7 +678,7 @@ status_t SampleTable::getMetaDataForSample( 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 @@ status_t SampleTable::getMetaDataForSample( *size = mSampleIterator->getSampleSize(); } - if (decodingTime) { - *decodingTime = mSampleIterator->getSampleTime(); + if (compositionTime) { + *compositionTime = mSampleIterator->getSampleTime(); } if (isSyncSample) { diff --git a/media/libstagefright/include/MPEG4Extractor.h b/media/libstagefright/include/MPEG4Extractor.h index d9ef208bda1c..3bd4c7eb5807 100644 --- a/media/libstagefright/include/MPEG4Extractor.h +++ b/media/libstagefright/include/MPEG4Extractor.h @@ -92,8 +92,6 @@ private: 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 212868236589..ed3e265c0cff 100644 --- a/media/libstagefright/include/NuCachedSource2.h +++ b/media/libstagefright/include/NuCachedSource2.h @@ -96,7 +96,9 @@ private: 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 2f95de9ce5ed..f44e0a2495f7 100644 --- a/media/libstagefright/include/SampleTable.h +++ b/media/libstagefright/include/SampleTable.h @@ -63,7 +63,7 @@ public: uint32_t sampleIndex, off64_t *offset, size_t *size, - uint32_t *decodingTime, + uint32_t *compositionTime, bool *isSyncSample = NULL); enum { @@ -107,6 +107,12 @@ private: 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 @@ private: 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 093538330f5d..27d8976d0702 100644 --- a/obex/javax/obex/ClientSession.java +++ b/obex/javax/obex/ClientSession.java @@ -449,8 +449,8 @@ public final class ClientSession extends ObexSession { 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 df0e0fb1c25e..8c12a20ba9bb 100644 --- a/obex/javax/obex/ObexHelper.java +++ b/obex/javax/obex/ObexHelper.java @@ -70,6 +70,12 @@ public final class ObexHelper { */ 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/libs/Android.mk b/opengl/libs/Android.mk index 7d72729e1e75..0747efb186ac 100644 --- a/opengl/libs/Android.mk +++ b/opengl/libs/Android.mk @@ -13,8 +13,8 @@ LOCAL_SRC_FILES:= \ 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 6474c87bbdef..9cf722381f53 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 @@ namespace android { 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_surface_t : public egl_object_t 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 @@ struct egl_context_t : public egl_object_t 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_context_t::Ref ContextRef; 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 @@ static void initEglTraceLevel() { 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 @@ static void initEglTraceLevel() { { 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 NATIVE* egl_to_native_cast(EGL arg) { } 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 @@ egl_context_t* get_context(EGLContext context) { 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 @@ EGLBoolean eglMakeCurrent( EGLDisplay dpy, EGLSurface draw, 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 fc40799724fc..9f6e68c4da3d 100644 --- a/opengl/libs/GLES2_dbg/Android.mk +++ b/opengl/libs/GLES2_dbg/Android.mk @@ -11,7 +11,7 @@ LOCAL_SRC_FILES := \ src/server.cpp \ src/vertex.cpp -LOCAL_C_INCLUDES := \ +LOCAL_C_INCLUDES := \ $(LOCAL_PATH) \ $(LOCAL_PATH)/../ \ external/stlport/stlport \ @@ -21,7 +21,8 @@ LOCAL_C_INCLUDES := \ #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 @@ endif 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 66c110f4a144..aeba213d57a4 100755 --- a/opengl/libs/GLES2_dbg/generate_api_cpp.py +++ b/opengl/libs/GLES2_dbg/generate_api_cpp.py @@ -26,36 +26,36 @@ def RemoveAnnotation(line): 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 @@ def generate_api(lines): 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 @@ def generate_api(lines): 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 @@ def generate_api(lines): 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 @@ def generate_api(lines): 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 @@ def generate_api(lines): 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 @@ def generate_api(lines): 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 @@ if __name__ == "__main__": ** 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 eac2292a8207..ee4208dd3dbf 100755 --- a/opengl/libs/GLES2_dbg/generate_caller_cpp.py +++ b/opengl/libs/GLES2_dbg/generate_caller_cpp.py @@ -177,7 +177,6 @@ const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & { 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 466c447cff2b..57e008cafddf 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 @@ message Message """) 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 @@ message Message 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 @@ message Message """) 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 130ca7e0489d..c4835478d2d7 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 @@ void Debug_glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, G 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 @@ void Debug_glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, G 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 @@ void Debug_glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint 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 @@ void Debug_glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint 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 @@ void Debug_glPolygonOffset(GLfloat factor, GLfloat units) 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 @@ void Debug_glShaderSource(GLuint shader, GLsizei count, const GLchar** string, c 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 @@ void Debug_glShaderSource(GLuint shader, GLsizei count, const GLchar** string, c 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 @@ void Debug_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsize 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 @@ void Debug_glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsize 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 @@ void Debug_glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoff 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 @@ void Debug_glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoff 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 b9fc341e3e61..0b227bc66c5c 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 9992f05fe669..6b72751bd009 100644 --- a/opengl/libs/GLES2_dbg/src/caller.cpp +++ b/opengl/libs/GLES2_dbg/src/caller.cpp @@ -105,7 +105,6 @@ const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & { 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 @@ const int * GenerateCall(DbgContext * const dbg, const glesv2debugger::Message & 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 54477575aa78..e8111b329224 100644 --- a/opengl/libs/GLES2_dbg/src/caller.h +++ b/opengl/libs/GLES2_dbg/src/caller.h @@ -138,7 +138,9 @@ static const int * GenerateCall_glGetProgramiv(DbgContext * const dbg, 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 @@ static const int * GenerateCall_glGetProgramInfoLog(DbgContext * const dbg, 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 @@ static const int * GenerateCall_glGetShaderiv(DbgContext * const dbg, 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 @@ static const int * GenerateCall_glGetShaderInfoLog(DbgContext * const dbg, 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 cc7336cf318f..7f5b27b3934c 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 @@ extern "C" 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 @@ DbgContext::~DbgContext() 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 @@ void DbgContext::Compress(const void * in_data, unsigned int in_len, { 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 @@ void DbgContext::Compress(const void * in_data, unsigned int in_len, } } +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::GetReadPixelsBuffer(const unsigned size) 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 @@ void DbgContext::CompressReadPixelBuffer(std::string * const outStr) 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 @@ void DbgContext::glUseProgram(GLuint program) 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::glVertexAttribPointer(GLuint indx, GLint size, GLenum type, 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 046c954000ee..50f70f7dff0e 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 @@ bool Message_Type_IsValid(int value) { case 0: case 1: case 2: + case 3: + case 4: return true; default: return false; @@ -445,7 +447,9 @@ bool Message_Type_IsValid(int value) { #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 @@ bool Message_Prop_IsValid(int value) { case 0: case 1: case 2: + case 3: + case 4: return true; default: return false; @@ -479,9 +485,11 @@ bool Message_Prop_IsValid(int value) { } #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::kDataFieldNumber; 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 @@ void Message::SharedCtor() { 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 @@ void Message::Clear() { 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 @@ bool Message::MergePartialFromCodedStream( 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 @@ bool Message::MergePartialFromCodedStream( 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 @@ bool Message::MergePartialFromCodedStream( } 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 @@ void Message::SerializeWithCachedSizes( } // 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 @@ void Message::SerializeWithCachedSizes( } // 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 @@ void Message::SerializeWithCachedSizes( ::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 @@ int Message::ByteSize() const { 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 @@ void Message::MergeFrom(const Message& from) { 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 @@ void Message::Swap(Message* other) { 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 b2ec5a0ac31b..5c946644a1a9 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 @@ const int Message_Function_Function_ARRAYSIZE = Message_Function_Function_MAX + 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 Message_DataType Message_DataType_DataType_MAX = Message_DataType_Nonrefer 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 @@ class Message : public ::google::protobuf::MessageLite { 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 @@ class Message : public ::google::protobuf::MessageLite { 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 @@ class Message : public ::google::protobuf::MessageLite { 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 @@ class Message : public ::google::protobuf::MessageLite { 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 @@ class Message : public ::google::protobuf::MessageLite { 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 @@ inline void Message::set_pixel_type(::google::protobuf::int32 value) { 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 3a20e21ba2cc..eb28d0607693 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 @@ EGLBoolean Debug_eglSwapBuffers(EGLDisplay dpy, EGLSurface draw) 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 9218da5b31b0..f2b1fa663598 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 GLFunctionBitfield { }; 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 @@ private: 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 @@ public: 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 @@ public: 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 @@ struct FunctionCall { }; // move these into DbgContext as static -extern bool capture; extern int timeMode; // SYSTEM_TIME_ extern int clientSock, serverSock; @@ -169,3 +176,5 @@ void SetProp(DbgContext * const dbg, const glesv2debugger::Message & cmd); 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 7039c8485036..0c711bf200fe 100644 --- a/opengl/libs/GLES2_dbg/src/server.cpp +++ b/opengl/libs/GLES2_dbg/src/server.cpp @@ -28,7 +28,8 @@ namespace android { 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 @@ static void Die(const char * msg) 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 @@ void StopDebugServer() 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 @@ void Receive(glesv2debugger::Message & cmd) 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 @@ void Receive(glesv2debugger::Message & cmd) bool TryReceive(glesv2debugger::Message & cmd) { + if (clientSock < 0) + return false; fd_set readSet; FD_ZERO(&readSet); FD_SET(clientSock, &readSet); @@ -146,14 +159,34 @@ bool TryReceive(glesv2debugger::Message & cmd) 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 @@ float Send(const glesv2debugger::Message & msg, glesv2debugger::Message & cmd) 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 @@ void SetProp(DbgContext * const dbg, const glesv2debugger::Message & cmd) 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 @@ int * MessageLoop(FunctionCall & functionCall, glesv2debugger::Message & msg, 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 @@ int * MessageLoop(FunctionCall & functionCall, glesv2debugger::Message & msg, 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 471e5adf9115..029ee3bf50a7 100644 --- a/opengl/libs/GLES2_dbg/src/vertex.cpp +++ b/opengl/libs/GLES2_dbg/src/vertex.cpp @@ -21,74 +21,13 @@ namespace android 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 Debug_glDrawArrays(GLenum mode, GLint first, GLsizei count) } 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 @@ void Debug_glDrawArrays(GLenum mode, GLint first, GLsizei count) 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 @@ void Debug_glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* 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 @@ void Debug_glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* 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 @@ void Debug_glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* 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 000000000000..14a84b447f61 --- /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 000000000000..058bea4d6f48 --- /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 000000000000..b6401e0c7979 --- /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 000000000000..617292e280e0 --- /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 000000000000..087989a06e12 --- /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 8029dcedf933..ee2c0112dd0d 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/services/input/Android.mk b/services/input/Android.mk index f9f8623f1c62..836c081c401d 100644 --- a/services/input/Android.mk +++ b/services/input/Android.mk @@ -23,7 +23,6 @@ LOCAL_SRC_FILES:= \ 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 6db445e1c553..1bc1bd1703a2 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,47 @@ static const float DRAG_MIN_SWITCH_SPEED = 50.0f; // pixels per second // 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 // 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 -// 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 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 INDETERMINATE_MULTITOUCH to SWIPE gesture mode can only occur when the +// 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 +135,8 @@ inline static float avg(float x, float y) { 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 +241,33 @@ static int32_t calculateEdgeFlagsUsingPointerBounds( 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 +1597,32 @@ void CursorInputMapper::sync(nsecs_t when) { 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 +1640,6 @@ void CursorInputMapper::sync(nsecs_t when) { } 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 +1803,8 @@ void TouchInputMapper::dump(String8& dump) { 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 +1877,10 @@ void TouchInputMapper::configureParameters() { 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 +2039,7 @@ bool TouchInputMapper::configureSurfaceLocked() { 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 +2234,39 @@ bool TouchInputMapper::configureSurfaceLocked() { 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 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; - // 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; + // Reset the current pointer gesture. + mPointerGesture.reset(); + + // Remove any current spots. + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } } } @@ -2628,6 +2693,11 @@ void TouchInputMapper::reset() { { // acquire lock AutoMutex _l(mLock); initializeLocked(); + + if (mPointerController != NULL + && mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerController->clearSpots(); + } } // release lock InputMapper::reset(); @@ -3070,7 +3140,7 @@ void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags, 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; @@ -3155,22 +3225,35 @@ void TouchInputMapper::prepareTouches(int32_t* outEdgeFlags, } void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlags) { + // 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); + // 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 + || 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( @@ -3284,11 +3367,8 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, uint32_t policyFlag // 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(); @@ -3328,77 +3408,51 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, // 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; + } + } else if (!mCurrentTouch.idBits.hasBit(activeTouchId)) { + activeTouchChanged = true; + if (!mCurrentTouch.idBits.isEmpty()) { + activeTouchId = mPointerGesture.activeTouchId = mCurrentTouch.idBits.firstMarkedBit(); + } else { + activeTouchId = mPointerGesture.activeTouchId = -1; } } - // 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); + // Determine whether we are in quiet time. + bool isQuietTime = false; + if (activeTouchId < 0) { + mPointerGesture.resetQuietTime(); } 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; + 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::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; } - } - 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; - } - } - - // 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; } } @@ -3413,10 +3467,17 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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. (CLICK_OR_DRAG) // The pointer follows the active touch point. // Emit DOWN, MOVE, UP events at the pointer location. // @@ -3449,7 +3510,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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,6 +3519,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, } if (bestId >= 0 && bestId != activeTouchId) { mPointerGesture.activeTouchId = activeTouchId = bestId; + activeTouchChanged = true; #if DEBUG_GESTURES LOGD("Gestures: CLICK_OR_DRAG switched pointers, " "bestId=%d, bestSpeed=%0.3f", bestId, bestSpeed); @@ -3474,6 +3536,10 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, * 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); } } @@ -3482,7 +3548,6 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerController->getPosition(&x, &y); mPointerGesture.currentGestureMode = PointerGesture::CLICK_OR_DRAG; - mPointerGesture.currentGesturePointerCount = 1; mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; @@ -3490,26 +3555,49 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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 mode. bool tapped = false; if (mPointerGesture.lastGestureMode == PointerGesture::HOVER - || mPointerGesture.lastGestureMode - == PointerGesture::INDETERMINATE_MULTITOUCH) { + && mLastTouch.pointerCount == 1) { if (when <= mPointerGesture.tapTime + 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.activeGestureId = 0; mPointerGesture.currentGestureMode = PointerGesture::TAP; - mPointerGesture.currentGesturePointerCount = 1; mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit( mPointerGesture.activeGestureId); @@ -3517,17 +3605,30 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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); + mPointerController->setButtonState(0); + + 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 { @@ -3537,14 +3638,22 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, #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) @@ -3565,6 +3674,9 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, * mLocked.pointerGestureXMovementScale; float deltaY = (currentPointer.y - lastPointer.y) * mLocked.pointerGestureYMovementScale; + + // Move the pointer using a relative motion. + // When using spots, the hover will occur at the position of the anchor spot. mPointerController->move(deltaX, deltaY); } @@ -3575,7 +3687,6 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, mPointerController->getPosition(&x, &y); mPointerGesture.currentGestureMode = PointerGesture::HOVER; - mPointerGesture.currentGesturePointerCount = 1; mPointerGesture.currentGestureIdBits.clear(); mPointerGesture.currentGestureIdBits.markBit(mPointerGesture.activeGestureId); mPointerGesture.currentGestureIdToIndex[mPointerGesture.activeGestureId] = 0; @@ -3586,159 +3697,268 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, if (mLastTouch.pointerCount == 0 && mCurrentTouch.pointerCount != 0) { mPointerGesture.tapTime = when; - mPointerGesture.initialPointerX = x; - mPointerGesture.initialPointerY = y; + mPointerGesture.tapX = x; + mPointerGesture.tapY = y; + } + + mPointerController->setButtonState(0); + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = 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. + // + // 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. // - // 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. + // 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; - - // Remember the initial pointer location. - // Everything we do will be relative to this location. - mPointerController->getPosition(&mPointerGesture.initialPointerX, - &mPointerGesture.initialPointerY); + mPointerGesture.currentGestureMode = PointerGesture::PRESS; + mPointerGesture.activeGestureId = 0; - // 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]]; - - 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); - - // Calculate the dot product of the vectors. + } 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 vx1, vy1, vx2, vy2; + mPointerGesture.velocityTracker.getVelocity(id1, &vx1, &vy1); + mPointerGesture.velocityTracker.getVelocity(id2, &vx2, &vy2); + + 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) { + // 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: PRESS 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(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, 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(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 +4002,7 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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 +4025,10 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, 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 +4047,45 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, "activeGestureId=%d", mPointerGesture.activeGestureId); #endif } - } else { - // INDETERMINATE_MULTITOUCH mode. - // Do nothing. -#if DEBUG_GESTURES - LOGD("Gestures: INDETERMINATE_MULTITOUCH"); -#endif + + mPointerController->setButtonState(0); // touch is not actually following the pointer + + if (mParameters.gestureMode == Parameters::GESTURE_MODE_SPOTS) { + mPointerGesture.spotGesture = PointerControllerInterface::SPOT_GESTURE_FREEFORM; + } } - } - // 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(); + // 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); @@ -3874,6 +4109,11 @@ void TouchInputMapper::preparePointerGestures(nsecs_t when, #endif } +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, int32_t action, int32_t flags, uint32_t metaState, int32_t edgeFlags, const PointerCoords* coords, const uint32_t* idToIndex, BitSet32 idBits, diff --git a/services/input/InputReader.h b/services/input/InputReader.h index 9ed1391301a7..9b2f4d2bb709 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 @@ public: /* 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; }; @@ -648,6 +644,20 @@ protected: 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 +680,12 @@ protected: 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 +857,8 @@ protected: float pointerGestureXZoomScale; float pointerGestureYZoomScale; - // The maximum swipe width squared. - int32_t pointerGestureMaxSwipeWidthSquared; + // The maximum swipe width. + float pointerGestureMaxSwipeWidth; } mLocked; virtual void configureParameters(); @@ -929,28 +945,32 @@ private: // 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,32 +979,20 @@ private: // 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; @@ -992,26 +1000,34 @@ private: // Time we started waiting for a tap gesture. nsecs_t tapTime; + // 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(); @@ -1035,6 +1051,7 @@ private: void dispatchPointerGestures(nsecs_t when, uint32_t policyFlags); void preparePointerGestures(nsecs_t when, bool* outCancelPreviousGesture, bool* outFinishPreviousGesture); + 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 15effb7f7951..ffef72057703 100644 --- a/services/input/PointerController.cpp +++ b/services/input/PointerController.cpp @@ -36,40 +36,49 @@ namespace android { // --- 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 spend fading out the pointer completely. -static const nsecs_t FADE_DURATION = 500 * 1000000LL; // 500 ms +// 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 wait between frames. -static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60; +// Time to spend fading out the pointer completely. +static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms -// Amount to subtract from alpha per frame. -static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION; +// --- PointerController --- -PointerController::PointerController(const sp<Looper>& looper, - const sp<SpriteController>& spriteController) : - mLooper(looper), mSpriteController(spriteController) { +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.buttonState = 0; - - mLocked.fadeAlpha = 1; - mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL; + mLocked.pointerAlpha = 0.0f; + mLocked.pointerSprite = mSpriteController->createSprite(); + mLocked.pointerIconChanged = false; - mLocked.visible = false; + mLocked.buttonState = 0; - mLocked.sprite = mSpriteController->createSprite(); + loadResources(); } PointerController::~PointerController() { @@ -77,7 +86,13 @@ PointerController::~PointerController() { 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 @@ void PointerController::setButtonState(uint32_t buttonState) { if (mLocked.buttonState != buttonState) { mLocked.buttonState = buttonState; - unfadeBeforeUpdateLocked(); - updateLocked(); } } @@ -167,8 +180,7 @@ void PointerController::setPositionLocked(float x, float y) { } else { mLocked.pointerY = y; } - unfadeBeforeUpdateLocked(); - updateLocked(); + updatePointerLocked(); } } @@ -182,32 +194,105 @@ void PointerController::getPosition(float* outX, float* outY) const { 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 @@ void PointerController::setDisplaySize(int32_t width, int32_t height) { mLocked.pointerY = 0; } - updateLocked(); + fadeOutAndReleaseAllSpotsLocked(); + updatePointerLocked(); } } @@ -283,74 +369,217 @@ void PointerController::setDisplayOrientation(int32_t orientation) { 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; } +} + +void PointerController::doAnimate() { + AutoMutex _l(mLock); + + bool keepAnimating = false; + mLocked.animationPending = false; + nsecs_t frameDelay = systemTime(SYSTEM_TIME_MONOTONIC) - mLocked.animationTime; + + // Animate pointer fade. + if (mLocked.pointerIsFading) { + mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION; + if (mLocked.pointerAlpha <= 0) { + mLocked.pointerAlpha = 0; + } else { + keepAnimating = true; + } + 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::unfadeBeforeUpdateLocked() { - sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); +void PointerController::doInactivityTimeout() { + AutoMutex _l(mLock); - if (isFadingLocked()) { - mLocked.visible = true; - mLocked.fadeAlpha = 1; - return true; // update required to effect the unfade + if (!mLocked.pointerIsFading) { + mLocked.pointerIsFading = true; + startAnimationLocked(); } - return false; // update not required } -void PointerController::startFadeLocked() { - if (!isFadingLocked()) { - sendFadeStepMessageDelayedLocked(0); +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::startInactivityFadeDelayLocked() { - if (!isFadingLocked()) { - sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked()); +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(); } -void PointerController::fadeStepLocked() { - if (mLocked.visible) { - mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME; - if (mLocked.fadeAlpha < 0) { - mLocked.fadeAlpha = 0; - mLocked.visible = false; - } else { - sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL); +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; } - updateLocked(); + } + 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(); } } -bool PointerController::isFadingLocked() { - return !mLocked.visible || mLocked.fadeAlpha != 1; +void PointerController::fadeOutAndReleaseAllSpotsLocked() { + for (size_t i = 0; i < mLocked.spots.size(); i++) { + Spot* spot = mLocked.spots.itemAt(i); + fadeOutAndReleaseSpotLocked(spot); + } } -nsecs_t PointerController::getInactivityFadeDelayTimeLocked() { - return mLocked.inactivityFadeDelay == INACTIVITY_FADE_DELAY_SHORT - ? INACTIVITY_FADE_DELAY_TIME_SHORT : INACTIVITY_FADE_DELAY_TIME_NORMAL; +void PointerController::loadResources() { + mPolicy->loadPointerResources(&mResources); } -void PointerController::sendFadeStepMessageDelayedLocked(nsecs_t delayTime) { - mLooper->removeMessages(mHandler, MSG_FADE_STEP); - mLooper->sendMessageDelayed(delayTime, mHandler, Message(MSG_FADE_STEP)); + +// --- 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 d467a5a01bcd..afd637116883 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,95 @@ public: /* 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, + // 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 +169,13 @@ protected: 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 +187,101 @@ public: 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; - uint32_t buttonState; + float pointerAlpha; + sp<Sprite> pointerSprite; + SpriteIcon pointerIcon; + bool pointerIconChanged; - float fadeAlpha; - InactivityFadeDelay inactivityFadeDelay; - - bool visible; + uint32_t buttonState; - 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.h b/services/input/SpotController.h deleted file mode 100644 index 1d091d7e2aa0..000000000000 --- 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 c6d4390fcc92..2fd1f0ab2715 100644 --- a/services/input/SpriteController.cpp +++ b/services/input/SpriteController.cpp @@ -36,6 +36,9 @@ namespace android { 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 @@ sp<Sprite> SpriteController::createSprite() { return new SpriteImpl(this); } +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 = mInvalidatedSprites.isEmpty(); - mInvalidatedSprites.push(sprite); + bool wasEmpty = mLocked.invalidatedSprites.isEmpty(); + mLocked.invalidatedSprites.push(sprite); if (wasEmpty) { - mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES)); + 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 @@ void SpriteController::doUpdateSprites() { { // 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 @@ void SpriteController::doUpdateSprites() { 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 @@ void SpriteController::doUpdateSprites() { 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 @@ void SpriteController::doUpdateSprites() { 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 @@ void SpriteController::doUpdateSprites() { && (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 @@ void SpriteController::doDisposeSurfaces() { // 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> SpriteController::obtainSurface(int32_t width, int32_t height 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 @@ sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height // --- SpriteController::SpriteImpl --- SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) : - mController(controller), mTransactionNestingCount(0) { + mController(controller) { } SpriteController::SpriteImpl::~SpriteImpl() { @@ -368,27 +397,33 @@ SpriteController::SpriteImpl::~SpriteImpl() { // 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); + uint32_t dirty; + if (icon.isValid()) { + icon.bitmap.copyTo(&mLocked.state.icon.bitmap, SkBitmap::kARGB_8888_Config); + + 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 { - mState.bitmap.reset(); - } - - uint32_t dirty = DIRTY_BITMAP; - if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) { - mState.hotSpotX = hotSpotX; - mState.hotSpotY = hotSpotY; - dirty |= DIRTY_HOTSPOT; + return; // setting to invalid icon and already invalid so nothing to do } invalidateLocked(dirty); @@ -397,8 +432,8 @@ void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap, 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::setVisible(bool visible) { 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::setPosition(float x, float y) { 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::setLayer(int32_t layer) { 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,38 +470,19 @@ void SpriteController::SpriteImpl::setTransformationMatrix( 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"); +void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { + bool wasDirty = mLocked.state.dirty; + mLocked.state.dirty |= dirty; - mTransactionNestingCount -= 1; - if (mTransactionNestingCount == 0 && mState.dirty) { + if (!wasDirty) { 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); - } - } -} - } // namespace android diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h index 27afb5ecbd82..50ae8a538869 100644 --- a/services/input/SpriteController.h +++ b/services/input/SpriteController.h @@ -33,6 +33,8 @@ namespace android { */ 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 @@ struct SpriteTransformationMatrix { }; /* + * 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 @@ protected: 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 @@ public: /* 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 @@ public: /* 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 @@ private: * 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 @@ private: 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 @@ private: 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 @@ private: 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 ba8ca9c399a4..54bb9d7d6ce5 100644 --- a/services/input/tests/InputReader_test.cpp +++ b/services/input/tests/InputReader_test.cpp @@ -97,6 +97,16 @@ private: 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 @@ private: 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 585369632a09..9ff52339499b 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.provider.Settings; 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } - 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // 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 class Tethering extends INetworkManagementEventObserver.Stub { 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - class StartingState extends HierarchicalState { + class StartingState extends State { @Override public void enter() { setAvailable(false); @@ -870,7 +871,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - class TetheredState extends HierarchicalState { + class TetheredState extends State { @Override public void enter() { IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE); @@ -1034,7 +1035,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - class UnavailableState extends HierarchicalState { + class UnavailableState extends State { @Override public void enter() { setAvailable(false); @@ -1064,7 +1065,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } - 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { // 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { 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 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } - 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 da3ebaf6c5d4..d10aa97b23ad 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -225,10 +225,12 @@ class Installer { 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 @@ class Installer { 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 a9d49b45c47e..6e1093f0ece0 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.ProviderInfo; 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 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { File miscDir = new File(dataDir, "misc"); miscDir.mkdirs(); mAppDataDir.mkdirs(); + mUserAppDataDir.mkdirs(); mDrmAppPrivateInstallDir.mkdirs(); } @@ -974,7 +984,8 @@ public class PackageManagerService extends IPackageManager.Stub { + " 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 @@ public class PackageManagerService extends IPackageManager.Stub { // 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 @@ public class PackageManagerService extends IPackageManager.Stub { } 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 @@ public class PackageManagerService extends IPackageManager.Stub { } } 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 @@ public class PackageManagerService extends IPackageManager.Stub { 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 index 2aeed7c35ef0..76fa5ab2e7ce 100644 --- a/services/java/com/android/server/pm/UserDetails.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -18,9 +18,13 @@ package com.android.server.pm; 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.XmlPullParser; 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 @@ public class UserDetails { 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 @@ public class UserDetails { 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 @@ public class UserDetails { 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 @@ public class UserDetails { } 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 @@ public class UserDetails { } } } + updateUserIds(); } catch (IOException ioe) { fallbackToSingleUser(); } catch (XmlPullParserException pe) { @@ -128,6 +148,7 @@ public class UserDetails { UserInfo primary = new UserInfo(0, "Primary", UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY); mUsers.put(0, primary); + updateUserIds(); writeUserList(); writeUser(primary); @@ -164,7 +185,7 @@ public class UserDetails { 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 @@ public class UserDetails { 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 @@ public class UserDetails { } 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 @@ public class UserDetails { 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 @@ public class UserDetails { // 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 @@ public class UserDetails { 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 b0978a3f251c..69bde41fc8e0 100644 --- a/services/java/com/android/server/wm/InputManager.java +++ b/services/java/com/android/server/wm/InputManager.java @@ -23,12 +23,6 @@ import org.xmlpull.v1.XmlPullParser; 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.InputChannel; 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 @@ public class InputManager { 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 @@ public class InputManager { 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 @@ public class InputManager { } } - 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 @@ public class InputManager { @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 aaa305e91dc2..1f10d9cd7af9 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 @@ static struct { jfieldID navigation; } gConfigurationClassInfo; -static struct { - jfieldID bitmap; - jfieldID hotSpotX; - jfieldID hotSpotY; -} gPointerIconClassInfo; - // --- Global functions --- @@ -128,17 +122,30 @@ static jobject getInputWindowHandleObjLocalRef(JNIEnv* env, 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: -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(jobject callbacksObj, const sp<Looper>& l NativeInputManager::~NativeInputManager() { JNIEnv* env = jniEnv(); + env->DeleteGlobalRef(mContextObj); env->DeleteGlobalRef(mCallbacksObj); } @@ -288,9 +302,13 @@ bool NativeInputManager::checkAndClearExceptionFromCallback(JNIEnv* env, const c 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) { + return; + } - if (mLocked.displayWidth != width || mLocked.displayHeight != height) { mLocked.displayWidth = width; mLocked.displayHeight = height; @@ -298,7 +316,7 @@ void NativeInputManager::setDisplaySize(int32_t displayId, int32_t width, int32_ if (controller != NULL) { controller->setDisplaySize(width, height); } - } + } // release lock } } @@ -428,40 +446,33 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32 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 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { 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 @@ bool NativeInputManager::checkInjectEventsPermissionNonReentrant( 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 bool checkInputManagerUnitialized(JNIEnv* env) { } 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 jstring android_server_InputManager_nativeDump(JNIEnv* env, jclass clazz) 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 @@ int register_android_server_InputManager(JNIEnv* env) { "getPointerLayer", "()I"); GET_METHOD_ID(gCallbacksClassInfo.getPointerIcon, clazz, - "getPointerIcon", "()Lcom/android/server/wm/InputManager$PointerIcon;"); + "getPointerIcon", "()Landroid/view/PointerIcon;"); // KeyEvent @@ -1421,19 +1444,6 @@ int register_android_server_InputManager(JNIEnv* env) { 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 64cff965b12f..a774841ddb6f 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -93,7 +93,11 @@ int DisplayHardware::getWidth() const { return mWidth; } 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 @@ void DisplayHardware::init(uint32_t dpy) 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 @@ void DisplayHardware::init(uint32_t dpy) 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 ee7a2af80e34..cdf89fd0badb 100644 --- a/services/surfaceflinger/DisplayHardware/DisplayHardware.h +++ b/services/surfaceflinger/DisplayHardware/DisplayHardware.h @@ -108,7 +108,7 @@ private: PixelFormat mFormat; uint32_t mFlags; mutable uint32_t mPageFlipCount; - GLint mMaxViewportDims; + GLint mMaxViewportDims[2]; GLint mMaxTextureSize; HWComposer* mHwc; diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 7b77aac2f9c5..e8188e74c1b1 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 android.test.AndroidTestCase; 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 class UserDetailsTest extends AndroidTestCase { } 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 class UserDetailsTest extends AndroidTestCase { } 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 class UserDetailsTest extends AndroidTestCase { } 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 @@ public class UserDetailsTest extends AndroidTestCase { } 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 0c47e860f0f0..791fbfdd08a3 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 @@ import java.util.HashMap; /** * {@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 @@ import java.util.HashMap; * * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { mDisconnectParams = dp; } - @Override protected void enter() { + @Override + public void enter() { mTag += 1; /** @@ -583,14 +584,16 @@ public abstract class DataConnection extends HierarchicalStateMachine { } } - @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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { } } - @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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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 @@ public abstract class DataConnection extends HierarchicalStateMachine { /** * 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/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java index 488794f4cdf8..56be570813de 100644 --- a/telephony/java/com/android/internal/telephony/Phone.java +++ b/telephony/java/com/android/internal/telephony/Phone.java @@ -159,8 +159,8 @@ public interface Phone { 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"; diff --git a/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java b/telephony/java/com/android/internal/telephony/cat/RilMessageDecoder.java index a197c9a5685a..2a1f508e4407 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.IccFileHandler; 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 @@ class RilMessageDecoder extends HierarchicalStateMachine { 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 @@ class RilMessageDecoder extends HierarchicalStateMachine { } } - 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/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 9a91ee41c8d6..fc1d536c20f2 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { * 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { @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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } @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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } } - 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 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { } } 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/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java index 3cde949f204c..9ae26daa9302 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 @@ import android.util.Log; * - 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 @@ class SupplicantStateTracker extends HierarchicalStateMachine { 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 @@ class SupplicantStateTracker extends HierarchicalStateMachine { * 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 @@ class SupplicantStateTracker extends HierarchicalStateMachine { * 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 SupplicantStateTracker extends HierarchicalStateMachine { } } - 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 SupplicantStateTracker extends HierarchicalStateMachine { } } - class CompletedState extends HierarchicalState { + class CompletedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -318,7 +318,7 @@ class SupplicantStateTracker extends HierarchicalStateMachine { } //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 46c07a303b90..16611d88851f 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -73,8 +73,8 @@ import android.util.LruCache; 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 @@ import java.util.regex.Pattern; * * @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 @@ public class WifiStateMachine extends HierarchicalStateMachine { 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 @@ public class WifiStateMachine extends HierarchicalStateMachine { * 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 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class InitialState extends HierarchicalState { + class InitialState extends State { @Override //TODO: could move logging into a common class public void enter() { @@ -1636,7 +1636,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverLoadingState extends HierarchicalState { + class DriverLoadingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1715,7 +1715,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverLoadedState extends HierarchicalState { + class DriverLoadedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1768,7 +1768,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverUnloadingState extends HierarchicalState { + class DriverUnloadingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1849,7 +1849,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverUnloadedState extends HierarchicalState { + class DriverUnloadedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1870,7 +1870,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverFailedState extends HierarchicalState { + class DriverFailedState extends State { @Override public void enter() { Log.e(TAG, getName() + "\n"); @@ -1884,7 +1884,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } - class SupplicantStartingState extends HierarchicalState { + class SupplicantStartingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1956,7 +1956,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class SupplicantStartedState extends HierarchicalState { + class SupplicantStartedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -1978,17 +1978,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { 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 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - 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 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverStartingState extends HierarchicalState { + class DriverStartingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2168,7 +2168,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverStartedState extends HierarchicalState { + class DriverStartedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2272,7 +2272,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverStoppingState extends HierarchicalState { + class DriverStoppingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2308,7 +2308,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DriverStoppedState extends HierarchicalState { + class DriverStoppedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2332,7 +2332,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class ScanModeState extends HierarchicalState { + class ScanModeState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2369,7 +2369,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class ConnectModeState extends HierarchicalState { + class ConnectModeState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2479,7 +2479,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class ConnectingState extends HierarchicalState { + class ConnectingState extends State { boolean mModifiedBluetoothCoexistenceMode; int mPowerMode; boolean mUseStaticIp; @@ -2677,7 +2677,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class ConnectedState extends HierarchicalState { + class ConnectedState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2789,7 +2789,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class DisconnectingState extends HierarchicalState { + class DisconnectingState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2819,7 +2819,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - 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 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - class WaitForWpsCompletionState extends HierarchicalState { + class WaitForWpsCompletionState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -2970,7 +2970,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { } } - 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 32d77a1c8420..120b22886de7 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 @@ import android.util.Log; * 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 @@ class WpsStateMachine extends HierarchicalStateMachine { 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 @@ class WpsStateMachine extends HierarchicalStateMachine { * 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 WpsStateMachine extends HierarchicalStateMachine { } } - class ActiveState extends HierarchicalState { + class ActiveState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); @@ -182,7 +182,7 @@ class WpsStateMachine extends HierarchicalStateMachine { } } - class InactiveState extends HierarchicalState { + class InactiveState extends State { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); |