diff options
43 files changed, 2052 insertions, 300 deletions
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c index 80ba1e9..9aa70a4 100644 --- a/cmds/installd/commands.c +++ b/cmds/installd/commands.c @@ -48,6 +48,11 @@ 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 e0d0f97..c062d36 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 cbca135..e5f6739 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 f37a6fb..3099b83 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 d058e38..78a450c 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -35,9 +35,9 @@ import android.content.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 ef8ba8e..85918cf 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/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fbf8f92..11cd446 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -35,6 +35,7 @@ import android.content.pm.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 99c4c7f..ff817c1 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 54a8842..564f4f4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -24,6 +24,7 @@ import android.content.res.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 0000000..2e7cb8f --- /dev/null +++ b/core/java/android/content/pm/UserInfo.aidl @@ -0,0 +1,20 @@ +/* +** +** Copyright 2011, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.content.pm; + +parcelable UserInfo; diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 3704d3a..ba5331c 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -74,8 +74,7 @@ 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 6a9b2dc..7efc264 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 2267015..dc5ff70 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 f4bb852..515218b 100644 --- a/core/java/android/speech/tts/SynthesisRequest.java +++ b/core/java/android/speech/tts/SynthesisRequest.java @@ -114,6 +114,11 @@ public abstract 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 a408ea2..da97fb4 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/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index af954c9..9d29a60 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/jni/android/graphics/Canvas.cpp b/core/jni/android/graphics/Canvas.cpp index 207c72f..310f02f 100644 --- a/core/jni/android/graphics/Canvas.cpp +++ b/core/jni/android/graphics/Canvas.cpp @@ -743,7 +743,11 @@ 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 4fac89a..a41c91b 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 088202e..77a731a 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/include/gui/SurfaceTexture.h b/include/gui/SurfaceTexture.h index 340daaf..96828c6 100644 --- a/include/gui/SurfaceTexture.h +++ b/include/gui/SurfaceTexture.h @@ -127,11 +127,28 @@ 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 df82bf2..fe9b049 100644 --- a/include/gui/SurfaceTextureClient.h +++ b/include/gui/SurfaceTextureClient.h @@ -27,6 +27,8 @@ namespace android { +class Surface; + class SurfaceTextureClient : public EGLNativeBase<ANativeWindow, SurfaceTextureClient, RefBase> { @@ -36,6 +38,7 @@ 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/libs/gui/SurfaceTexture.cpp b/libs/gui/SurfaceTexture.cpp index e2346f0..39418f0 100644 --- a/libs/gui/SurfaceTexture.cpp +++ b/libs/gui/SurfaceTexture.cpp @@ -27,6 +27,8 @@ #include <gui/SurfaceTexture.h> +#include <hardware/hardware.h> + #include <surfaceflinger/ISurfaceComposer.h> #include <surfaceflinger/SurfaceComposerClient.h> #include <surfaceflinger/IGraphicBufferAlloc.h> @@ -82,6 +84,7 @@ 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 29fc4d3..f4b2416 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/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp index 1ca2d6d..f9db1a1 100644 --- a/media/libstagefright/MPEG4Extractor.cpp +++ b/media/libstagefright/MPEG4Extractor.cpp @@ -377,7 +377,7 @@ 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 c1aa46e..dc86885 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 423df70..08db902 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 d9ef208..3bd4c7e 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 2128682..ed3e265c 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 2f95de9..f44e0a2 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/opengl/libs/GLES2_dbg/Android.mk b/opengl/libs/GLES2_dbg/Android.mk index 853cce6..9f6e68c 100644 --- a/opengl/libs/GLES2_dbg/Android.mk +++ b/opengl/libs/GLES2_dbg/Android.mk @@ -45,3 +45,5 @@ LOCAL_MODULE:= libGLESv2_dbg LOCAL_MODULE_TAGS := optional include $(BUILD_SHARED_LIBRARY) + +include $(LOCAL_PATH)/test/Android.mk diff --git a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp index fe93874..71892d3 100644 --- a/opengl/libs/GLES2_dbg/src/dbgcontext.cpp +++ b/opengl/libs/GLES2_dbg/src/dbgcontext.cpp @@ -25,11 +25,11 @@ extern "C" namespace android { -static pthread_key_t sEGLThreadLocalStorageKey = -1; +pthread_key_t dbgEGLThreadLocalStorageKey = -1; DbgContext * getDbgContextThreadSpecific() { - tls_t* tls = (tls_t*)pthread_getspecific(sEGLThreadLocalStorageKey); + tls_t* tls = (tls_t*)pthread_getspecific(dbgEGLThreadLocalStorageKey); return tls->dbg; } @@ -63,7 +63,7 @@ DbgContext::~DbgContext() DbgContext * CreateDbgContext(const pthread_key_t EGLThreadLocalStorageKey, const unsigned version, const gl_hooks_t * const hooks) { - sEGLThreadLocalStorageKey = EGLThreadLocalStorageKey; + dbgEGLThreadLocalStorageKey = EGLThreadLocalStorageKey; assert(version < 2); assert(GL_NO_ERROR == hooks->gl.glGetError()); GLint MAX_VERTEX_ATTRIBS = 0; @@ -149,6 +149,37 @@ 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) { diff --git a/opengl/libs/GLES2_dbg/src/header.h b/opengl/libs/GLES2_dbg/src/header.h index c9e6c41..f2b1fa6 100644 --- a/opengl/libs/GLES2_dbg/src/header.h +++ b/opengl/libs/GLES2_dbg/src/header.h @@ -73,8 +73,9 @@ struct GLFunctionBitfield { }; struct DbgContext { -private: static const unsigned int LZF_CHUNK_SIZE = 256 * 1024; + +private: char * lzf_buf; // malloc / free; for lzf chunk compression and other uses // used as buffer and reference frame for ReadPixels; malloc/free @@ -129,6 +130,8 @@ public: 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]; diff --git a/opengl/libs/GLES2_dbg/src/server.cpp b/opengl/libs/GLES2_dbg/src/server.cpp index f13d6cc..ba4960d 100644 --- a/opengl/libs/GLES2_dbg/src/server.cpp +++ b/opengl/libs/GLES2_dbg/src/server.cpp @@ -186,7 +186,7 @@ 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); @@ -246,8 +246,9 @@ int * MessageLoop(FunctionCall & functionCall, glesv2debugger::Message & msg, msg.set_function(function); // 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(false); + cmd.set_expect_response(expectResponse); glesv2debugger::Message_Function oldCmd = cmd.function(); Send(msg, cmd); expectResponse = cmd.expect_response(); diff --git a/opengl/libs/GLES2_dbg/src/vertex.cpp b/opengl/libs/GLES2_dbg/src/vertex.cpp index 7edc050..029ee3b 100644 --- a/opengl/libs/GLES2_dbg/src/vertex.cpp +++ b/opengl/libs/GLES2_dbg/src/vertex.cpp @@ -43,10 +43,8 @@ void Debug_glDrawArrays(GLenum mode, GLint first, GLsizei count) void * pixels = NULL; int viewport[4] = {}; - if (!expectResponse) { - cmd.set_function(glesv2debugger::Message_Function_CONTINUE); - cmd.set_expect_response(false); - } + 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(); @@ -61,8 +59,6 @@ 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) - cmd.set_function(glesv2debugger::Message_Function_SKIP); if (!expectResponse) { cmd.set_function(glesv2debugger::Message_Function_SKIP); cmd.set_expect_response(false); @@ -154,10 +150,8 @@ void Debug_glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* void * pixels = NULL; int viewport[4] = {}; - if (!expectResponse) { - cmd.set_function(glesv2debugger::Message_Function_CONTINUE); - cmd.set_expect_response(false); - } + 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(); @@ -172,8 +166,6 @@ 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) - cmd.set_function(glesv2debugger::Message_Function_SKIP); if (!expectResponse) { cmd.set_function(glesv2debugger::Message_Function_SKIP); cmd.set_expect_response(false); diff --git a/opengl/libs/GLES2_dbg/test/Android.mk b/opengl/libs/GLES2_dbg/test/Android.mk new file mode 100644 index 0000000..14a84b4 --- /dev/null +++ b/opengl/libs/GLES2_dbg/test/Android.mk @@ -0,0 +1,39 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/../src \ + $(LOCAL_PATH)/../../ \ + external/gtest/include \ + external/stlport/stlport \ + external/protobuf/src \ + bionic \ + external \ +# + +LOCAL_SRC_FILES:= \ + test_main.cpp \ + test_server.cpp \ + test_socket.cpp \ +# + +LOCAL_SHARED_LIBRARIES := libcutils libutils libGLESv2_dbg libstlport +LOCAL_STATIC_LIBRARIES := libgtest libprotobuf-cpp-2.3.0-lite liblzf +LOCAL_MODULE_TAGS := tests +LOCAL_MODULE:= libGLESv2_dbg_test + +ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) + LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER +endif +ifneq ($(TARGET_SIMULATOR),true) + LOCAL_C_INCLUDES += bionic/libc/private +endif + +LOCAL_CFLAGS += -DLOG_TAG=\"libEGL\" +LOCAL_CFLAGS += -DGL_GLEXT_PROTOTYPES -DEGL_EGLEXT_PROTOTYPES +LOCAL_CFLAGS += -fvisibility=hidden + +include $(BUILD_EXECUTABLE) + diff --git a/opengl/libs/GLES2_dbg/test/test_main.cpp b/opengl/libs/GLES2_dbg/test/test_main.cpp new file mode 100644 index 0000000..058bea4 --- /dev/null +++ b/opengl/libs/GLES2_dbg/test/test_main.cpp @@ -0,0 +1,234 @@ +/* + ** Copyright 2011, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "header.h" +#include "gtest/gtest.h" +#include "hooks.h" + +namespace +{ + +// The fixture for testing class Foo. +class DbgContextTest : public ::testing::Test +{ +protected: + android::DbgContext dbg; + gl_hooks_t hooks; + + DbgContextTest() + : dbg(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE) { + // You can do set-up work for each test here. + hooks.gl.glGetError = GetError; + } + + static GLenum GetError() { + return GL_NO_ERROR; + } + + virtual ~DbgContextTest() { + // You can do clean-up work that doesn't throw exceptions here. + } + + // If the constructor and destructor are not enough for setting up + // and cleaning up each test, you can define the following methods: + + virtual void SetUp() { + // Code here will be called immediately after the constructor (right + // before each test). + } + + virtual void TearDown() { + // Code here will be called immediately after each test (right + // before the destructor). + } +}; + +TEST_F(DbgContextTest, GetReadPixelBuffer) +{ + const unsigned int bufferSize = 512; + // test that it's allocating two buffers and swapping them + void * const buffer0 = dbg.GetReadPixelsBuffer(bufferSize); + ASSERT_NE((void *)NULL, buffer0); + for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) { + EXPECT_EQ(0, ((unsigned int *)buffer0)[i]) + << "GetReadPixelsBuffer should allocate and zero"; + ((unsigned int *)buffer0)[i] = i * 13; + } + + void * const buffer1 = dbg.GetReadPixelsBuffer(bufferSize); + ASSERT_NE((void *)NULL, buffer1); + EXPECT_NE(buffer0, buffer1); + for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) { + EXPECT_EQ(0, ((unsigned int *)buffer1)[i]) + << "GetReadPixelsBuffer should allocate and zero"; + ((unsigned int *)buffer1)[i] = i * 17; + } + + void * const buffer2 = dbg.GetReadPixelsBuffer(bufferSize); + EXPECT_EQ(buffer2, buffer0); + for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) + EXPECT_EQ(i * 13, ((unsigned int *)buffer2)[i]) + << "GetReadPixelsBuffer should swap buffers"; + + void * const buffer3 = dbg.GetReadPixelsBuffer(bufferSize); + EXPECT_EQ(buffer3, buffer1); + for (unsigned int i = 0; i < bufferSize / sizeof(unsigned int); i++) + EXPECT_EQ(i * 17, ((unsigned int *)buffer3)[i]) + << "GetReadPixelsBuffer should swap buffers"; + + void * const buffer4 = dbg.GetReadPixelsBuffer(bufferSize); + EXPECT_NE(buffer3, buffer4); + EXPECT_EQ(buffer0, buffer2); + EXPECT_EQ(buffer1, buffer3); + EXPECT_EQ(buffer2, buffer4); + + // it reallocs as necessary; 0 size may result in NULL + for (unsigned int i = 0; i < 42; i++) { + void * const buffer = dbg.GetReadPixelsBuffer(((i & 7)) << 20); + EXPECT_NE((void *)NULL, buffer) + << "should be able to get a variety of reasonable sizes"; + EXPECT_TRUE(dbg.IsReadPixelBuffer(buffer)); + } +} + +TEST_F(DbgContextTest, CompressReadPixelBuffer) +{ + const unsigned int bufferSize = dbg.LZF_CHUNK_SIZE * 4 + 33; + std::string out; + unsigned char * buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + buffer[i] = i * 13; + dbg.CompressReadPixelBuffer(&out); + uint32_t decompSize = 0; + ASSERT_LT(12, out.length()); // at least written chunk header + ASSERT_EQ(bufferSize, *(uint32_t *)out.data()) + << "total decompressed size should be as requested in GetReadPixelsBuffer"; + for (unsigned int i = 4; i < out.length();) { + const uint32_t outSize = *(uint32_t *)(out.data() + i); + i += 4; + const uint32_t inSize = *(uint32_t *)(out.data() + i); + i += 4; + if (inSize == 0) + i += outSize; // chunk not compressed + else + i += inSize; // skip the actual compressed chunk + decompSize += outSize; + } + ASSERT_EQ(bufferSize, decompSize); + decompSize = 0; + + unsigned char * decomp = dbg.Decompress(out.data(), out.length(), &decompSize); + ASSERT_EQ(decompSize, bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + EXPECT_EQ((unsigned char)(i * 13), decomp[i]) << "xor with 0 ref is identity"; + free(decomp); + + buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + buffer[i] = i * 13; + out.clear(); + dbg.CompressReadPixelBuffer(&out); + decompSize = 0; + decomp = dbg.Decompress(out.data(), out.length(), &decompSize); + ASSERT_EQ(decompSize, bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + EXPECT_EQ(0, decomp[i]) << "xor with same ref is 0"; + free(decomp); + + buffer = (unsigned char *)dbg.GetReadPixelsBuffer(bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + buffer[i] = i * 19; + out.clear(); + dbg.CompressReadPixelBuffer(&out); + decompSize = 0; + decomp = dbg.Decompress(out.data(), out.length(), &decompSize); + ASSERT_EQ(decompSize, bufferSize); + for (unsigned int i = 0; i < bufferSize; i++) + EXPECT_EQ((unsigned char)(i * 13) ^ (unsigned char)(i * 19), decomp[i]) + << "xor ref"; + free(decomp); +} + +TEST_F(DbgContextTest, UseProgram) +{ + static const GLuint _program = 74568; + static const struct Attribute { + const char * name; + GLint location; + GLint size; + GLenum type; + } _attributes [] = { + {"aaa", 2, 2, GL_FLOAT_VEC2}, + {"bb", 6, 2, GL_FLOAT_MAT2}, + {"c", 1, 1, GL_FLOAT}, + }; + static const unsigned int _attributeCount = sizeof(_attributes) / sizeof(*_attributes); + struct GL { + static void GetProgramiv(GLuint program, GLenum pname, GLint* params) { + EXPECT_EQ(_program, program); + ASSERT_NE((GLint *)NULL, params); + switch (pname) { + case GL_ACTIVE_ATTRIBUTES: + *params = _attributeCount; + return; + case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: + *params = 4; // includes NULL terminator + return; + default: + ADD_FAILURE() << "not handled pname: " << pname; + } + } + + static GLint GetAttribLocation(GLuint program, const GLchar* name) { + EXPECT_EQ(_program, program); + for (unsigned int i = 0; i < _attributeCount; i++) + if (!strcmp(name, _attributes[i].name)) + return _attributes[i].location; + ADD_FAILURE() << "unknown attribute name: " << name; + return -1; + } + + static void GetActiveAttrib(GLuint program, GLuint index, GLsizei bufsize, + GLsizei* length, GLint* size, GLenum* type, GLchar* name) { + EXPECT_EQ(_program, program); + ASSERT_LT(index, _attributeCount); + const Attribute & att = _attributes[index]; + ASSERT_GE(bufsize, strlen(att.name) + 1); + ASSERT_NE((GLint *)NULL, size); + ASSERT_NE((GLenum *)NULL, type); + ASSERT_NE((GLchar *)NULL, name); + strcpy(name, att.name); + if (length) + *length = strlen(name) + 1; + *size = att.size; + *type = att.type; + } + }; + hooks.gl.glGetProgramiv = GL::GetProgramiv; + hooks.gl.glGetAttribLocation = GL::GetAttribLocation; + hooks.gl.glGetActiveAttrib = GL::GetActiveAttrib; + dbg.glUseProgram(_program); + EXPECT_EQ(10, dbg.maxAttrib); + dbg.glUseProgram(0); + EXPECT_EQ(0, dbg.maxAttrib); +} +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/opengl/libs/GLES2_dbg/test/test_server.cpp b/opengl/libs/GLES2_dbg/test/test_server.cpp new file mode 100644 index 0000000..7fb87ea --- /dev/null +++ b/opengl/libs/GLES2_dbg/test/test_server.cpp @@ -0,0 +1,197 @@ +/* + ** 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; + } +}; + + + +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()); +} + +void * glNoop() +{ + return 0; +} + +class ServerFileContextTest : public ServerFileTest +{ +protected: + tls_t tls; + gl_hooks_t hooks; + + ServerFileContextTest() { } + + virtual ~ServerFileContextTest() { } + + virtual void SetUp() { + ServerFileTest::SetUp(); + + if (dbgEGLThreadLocalStorageKey == -1) + pthread_key_create(&dbgEGLThreadLocalStorageKey, NULL); + ASSERT_NE(-1, dbgEGLThreadLocalStorageKey); + tls.dbg = new DbgContext(1, &hooks, 32, GL_RGBA, GL_UNSIGNED_BYTE); + ASSERT_NE((void *)NULL, tls.dbg); + pthread_setspecific(dbgEGLThreadLocalStorageKey, &tls); + for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++) + ((void **)&hooks)[i] = reinterpret_cast<void *>(glNoop); + } + + virtual void TearDown() { + ServerFileTest::TearDown(); + } +}; + +TEST_F(ServerFileContextTest, MessageLoop) +{ + static const int arg0 = 45; + static const float arg7 = -87.2331f; + static const int arg8 = -3; + static const int * ret = reinterpret_cast<int *>(870); + + struct Caller : public FunctionCall { + const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) { + msg.set_arg0(arg0); + msg.set_arg7((int &)arg7); + msg.set_arg8(arg8); + return ret; + } + } caller; + const int contextId = reinterpret_cast<int>(tls.dbg); + glesv2debugger::Message msg, read; + + EXPECT_EQ(ret, MessageLoop(caller, msg, msg.glFinish)); + + rewind(file); + Read(read); + EXPECT_EQ(contextId, read.context_id()); + EXPECT_EQ(read.glFinish, read.function()); + EXPECT_EQ(false, read.expect_response()); + EXPECT_EQ(read.BeforeCall, read.type()); + + Read(read); + EXPECT_EQ(contextId, read.context_id()); + EXPECT_EQ(read.glFinish, read.function()); + EXPECT_EQ(false, read.expect_response()); + EXPECT_EQ(read.AfterCall, read.type()); + EXPECT_TRUE(read.has_time()); + EXPECT_EQ(arg0, read.arg0()); + const int readArg7 = read.arg7(); + EXPECT_EQ(arg7, (float &)readArg7); + EXPECT_EQ(arg8, read.arg8()); + + const long pos = ftell(file); + fseek(file, 0, SEEK_END); + EXPECT_EQ(pos, ftell(file)) + << "should only write the BeforeCall and AfterCall messages"; +} + +TEST_F(ServerFileContextTest, DisableEnableVertexAttribArray) +{ + Debug_glEnableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 2); // should just ignore invalid index + + glesv2debugger::Message read; + rewind(file); + Read(read); + EXPECT_EQ(read.glEnableVertexAttribArray, read.function()); + EXPECT_EQ(tls.dbg->MAX_VERTEX_ATTRIBS + 2, read.arg0()); + Read(read); + + rewind(file); + Debug_glDisableVertexAttribArray(tls.dbg->MAX_VERTEX_ATTRIBS + 4); // should just ignore invalid index + rewind(file); + Read(read); + Read(read); + + for (unsigned int i = 0; i < tls.dbg->MAX_VERTEX_ATTRIBS; i += 5) { + rewind(file); + Debug_glEnableVertexAttribArray(i); + EXPECT_TRUE(tls.dbg->vertexAttribs[i].enabled); + rewind(file); + Read(read); + EXPECT_EQ(read.glEnableVertexAttribArray, read.function()); + EXPECT_EQ(i, read.arg0()); + Read(read); + + rewind(file); + Debug_glDisableVertexAttribArray(i); + EXPECT_FALSE(tls.dbg->vertexAttribs[i].enabled); + rewind(file); + Read(read); + EXPECT_EQ(read.glDisableVertexAttribArray, read.function()); + EXPECT_EQ(i, read.arg0()); + Read(read); + } +} diff --git a/opengl/libs/GLES2_dbg/test/test_socket.cpp b/opengl/libs/GLES2_dbg/test/test_socket.cpp new file mode 100644 index 0000000..1c31a94 --- /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_NE((void *)NULL, tls.dbg); + 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_NE((char *)NULL, buffer); + } + 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_NE((void *)NULL, pixels); + 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/services/java/com/android/server/pm/Installer.java b/services/java/com/android/server/pm/Installer.java index da3ebaf..d10aa97 100644 --- a/services/java/com/android/server/pm/Installer.java +++ b/services/java/com/android/server/pm/Installer.java @@ -225,10 +225,12 @@ 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 a9d49b4..6e1093f 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -65,6 +65,7 @@ import android.content.pm.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 2aeed7c..76fa5ab 100644 --- a/services/java/com/android/server/pm/UserDetails.java +++ b/services/java/com/android/server/pm/UserManager.java @@ -18,9 +18,13 @@ 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/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 7b77aac..e8188e7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -16,7 +16,7 @@ package com.android.server.pm; -import com.android.server.pm.UserDetails; +import com.android.server.pm.UserManager; import android.content.pm.UserInfo; import android.os.Debug; @@ -25,23 +25,24 @@ import 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/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index f7157d4..16611d8 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -1978,17 +1978,6 @@ public class WifiStateMachine extends StateMachine { 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 */ @@ -2089,6 +2078,17 @@ public class WifiStateMachine extends StateMachine { 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) { |